No project description provided
Project description
Dubai Real-Estate Lead Qualification Agent
A conversational AI agent built with agents-builder, LangGraph, and Ollama.
The agent talks naturally with a prospective buyer, extracts useful details
from the full conversation, chooses the next qualification question, and
returns a scored lead when enough information is available.
The conversation is not a rigid form. A customer can provide several details in one message, use approximate language, name any Dubai area, or describe a property in their own words.
Install
This project uses the local editable agents-builder checkout at
../agents-builder.
uv sync --group dev --all-extras
Agent Structure
src/real_estate_lead_agent/
├── agent.py # QualificationAgent and LangGraph wiring
├── nodes.py # QualificationNode turn processing
├── prompts.py # Structured qualification prompt
├── models.py # Lead and LLM output schemas
├── scoring.py # Deterministic lead scoring
├── integrations/
│ ├── __init__.py # Generic interface and component loading
│ ├── settings.py # Discriminated integration settings
│ ├── csv.py # CSV integration
│ └── google_sheets.py # Google Sheets integration
├── cli.py # Interactive agent runner
└── adapters/
├── conversation.py # Shared per-session conversation state
├── telegram.py # Telegram bot adapter
└── web_api.py # FastAPI web chat adapter
The modules follow the same separation used by the agents-builder agent
template: state contracts, prompts, nodes, and graph composition remain
independent and directly testable.
Configure Ollama
The default configuration is:
configs/qualification-agent.example.yaml
It follows the Paper Buddy Ollama configuration style:
agent:
module_path: real_estate_lead_agent.agent.QualificationAgent
llm_factory:
module_path: agents_builder.llm.factory.LLMFactory
roles:
qualification:
module_path: agents_builder.llm.llm.OllamaLLM
model: gpt-oss:120b-cloud
base_url: https://ollama.com
kind: ollama
scoring:
module_path: agents_builder.llm.llm.OllamaLLM
model: gpt-oss:120b-cloud
base_url: https://ollama.com
kind: ollama
mcps: []
store:
kind: memory
integrations:
- kind: csv
enabled: true
path: leads.csv
- kind: google_sheets
enabled: false
spreadsheet_id: your-google-spreadsheet-id
range: Leads!A:O
credentials_file: credentials/google-service-account.json
For a local Ollama model, change model and base_url, for example:
model: gpt-oss:20b
base_url: http://localhost:11434
Run The Agent
uv run real-estate-lead-agent
Or provide another config:
uv run real-estate-lead-agent --config configs/qualification-agent.example.yaml
Qualified leads are sent to every enabled integration. The example configuration
appends them to leads.csv. Choose an additional CSV destination with:
uv run real-estate-lead-agent --output data/qualified-leads.csv
The command starts the real AI conversation. The agent can understand input such as:
I want a modern two-bedroom apartment near the beach, around 3 million AED,
and I hope to buy in the next two months.
It extracts all available facts, asks one useful follow-up question, and keeps
the accumulated lead profile. Once the minimum profile is known, it prints the
final lead JSON with lead_score and lead_status, then sends it to the configured integrations.
Run As A Telegram Bot
- Open @BotFather, send
/newbot, and follow the prompts to create a bot and receive its token. - Create a
.envfile in the project root. Do not commit it:
TELEGRAM_BOT_TOKEN=your-bot-token
- Start the bot with long polling:
uv run real-estate-lead-telegram
To use another agent configuration:
uv run real-estate-lead-telegram --config configs/qualification-agent.example.yaml
Use a different dotenv file with --env-file path/to/.env. An already exported
TELEGRAM_BOT_TOKEN takes precedence over the dotenv value.
Open the bot in Telegram and send /start. The bot sends the welcome and first qualification question as two separate
messages, in that order, before the user replies. Each Telegram chat gets independent
conversation state. /reset starts that chat again, and completed leads are
sent to the same enabled CSV or Google Sheets integrations used by the CLI. The final
Telegram response confirms how many integrations received the completed lead.
Run As A Web API
Start the FastAPI adapter:
uv run real-estate-lead-web-api \
--allowed-origin http://localhost:3000
Use --allowed-origin more than once when the widget is hosted on multiple
domains. The default bind address is 127.0.0.1:8000; use --host 0.0.0.0
when running inside a container.
The API exposes:
GET /api/health
POST /api/chat/start
POST /api/chat/message
POST /api/chat/reset
GET /api/docs
Start a session:
curl -X POST http://localhost:8000/api/chat/start
Send a message using the returned session ID:
curl -X POST http://localhost:8000/api/chat/message \
-H 'Content-Type: application/json' \
-d '{
"session_id": "fb94bb2e-52a0-48ec-9d29-b50a2c23419f",
"message": "I want a two-bedroom apartment in Dubai Marina"
}'
The response contains session_id, message, and completed. Sessions are stored in memory by default. Configure the validated store section
as kind: redis with redis_url for multi-instance production deployments. See deploy/cloud/gcp/README.md
for the Cloud Run and Memorystore deployment procedure.
Google Sheets Integration
Create a Google Cloud service account, enable the Google Sheets API, and share
the target spreadsheet with the service account email. Then enable the
google_sheets integration and set its spreadsheet ID and range:
integrations:
- kind: google_sheets
enabled: true
spreadsheet_id: 1abc123...
range: Leads!A:O
credentials_file: credentials/google-service-account.json
When credentials_file is omitted, Application Default Credentials are used.
Each qualified lead is appended as one row in the documented eight-column result
order.
Additional providers define an IntegrationSettings subclass with a unique kind
and module_path, add it to the discriminated ConfiguredIntegrationSettings
union, and implement ResultIntegration[SettingsType].send. Runtime components
are loaded with load_class(config.module_path).from_config(config).
The qualification schema contains six fields:
| Field |
|---|
first_name |
phone |
budget |
property_type |
location |
timeline |
The agent asks only about unresolved fields. A field is considered
addressed even when the customer does not know the answer, refuses to provide it, or
gives an invalid value. In those cases its value is null.
Every final JSON result contains the six lead fields in the documented order, followed
by lead_score and lead_status. The CSV uses the same fixed columns; null values are
written as empty cells.
Scoring Strategies
The agent uses LLMLeadScorer by default. After qualification, the scoring
model receives both the normalized lead and the full customer conversation. It
returns a score from 0 to 100, a HOT/WARM/COLD status, and concise
reasoning. Reasoning is validated internally but is not added to the final CSV.
A deterministic alternative is also available:
from real_estate_lead_agent import CodeBasedLeadScorer, QualificationAgent
agent = QualificationAgent(config, scorer=CodeBasedLeadScorer())
CodeBasedLeadScorer applies fixed budget, timeline, and completeness rules. Both scorers return the same LeadQualificationResult
schema, so JSON and CSV output do not change.
Quality
make ci
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file real_estate_lead_agent-1.0.0.tar.gz.
File metadata
- Download URL: real_estate_lead_agent-1.0.0.tar.gz
- Upload date:
- Size: 127.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7148e50b66830f8b9642a31f3a3da5d740a3d1a1ede99faa278adb7476df4ba1
|
|
| MD5 |
137dfeafc07a2bd990070b54714574e6
|
|
| BLAKE2b-256 |
22e5c590683542084539d67192124006dfd6cbad704ff9bf28fff69d3f1f74bb
|
File details
Details for the file real_estate_lead_agent-1.0.0-py3-none-any.whl.
File metadata
- Download URL: real_estate_lead_agent-1.0.0-py3-none-any.whl
- Upload date:
- Size: 26.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ca772184f9095a16e7e69ef521166f73d87ac757594d4c1064fd892b045e7f5
|
|
| MD5 |
7d79eadf54d2700087cb26e00bd04578
|
|
| BLAKE2b-256 |
d656dd53287a7703f9e74041ab7c84f9cfb1280868fafe392c21256fed517cb1
|