Metadata-Version: 2.4
Name: real-estate-lead-agent
Version: 1.0.0
Author-email: jalal <jalalkhaldi3@gmail.com>
Requires-Python: <3.13,>=3.12
Requires-Dist: agents-builder<5.0.0,>=4.0.0
Requires-Dist: fastapi>=0.137.0
Requires-Dist: google-api-python-client>=2.197.0
Requires-Dist: google-auth>=2.53.0
Requires-Dist: python-dotenv>=1.2.2
Requires-Dist: python-telegram-bot<23,>=21.5
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: redis<8.0,>=6.0
Requires-Dist: uvicorn>=0.49.0
Description-Content-Type: text/markdown

# 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`.

```bash
uv sync --group dev --all-extras
```

## Agent Structure

```text
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:

```text
configs/qualification-agent.example.yaml
```

It follows the Paper Buddy Ollama configuration style:

```yaml
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:

```yaml
model: gpt-oss:20b
base_url: http://localhost:11434
```

## Run The Agent

```bash
uv run real-estate-lead-agent
```

Or provide another config:

```bash
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:

```bash
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:

```text
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

1. Open [@BotFather](https://t.me/BotFather), send `/newbot`, and follow the
   prompts to create a bot and receive its token.
2. Create a `.env` file in the project root. Do not commit it:

```dotenv
TELEGRAM_BOT_TOKEN=your-bot-token
```

3. Start the bot with long polling:

```bash
uv run real-estate-lead-telegram
```

To use another agent configuration:

```bash
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:

```bash
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:

```text
GET  /api/health
POST /api/chat/start
POST /api/chat/message
POST /api/chat/reset
GET  /api/docs
```

Start a session:

```bash
curl -X POST http://localhost:8000/api/chat/start
```

Send a message using the returned session ID:

```bash
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`](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:

```yaml
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:

```python
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

```bash
make ci
```
