Housing Price Agent
A natural-language AI agent that monitors and analyzes real-estate listing prices from public web pages, on demand.
The agent receives a question and (optionally) one or more listing-page URLs. It orchestrates a set of tools to extract listings, filter them, convert currencies, and compute price statistics, then returns a short natural-language report.
OPENAI_MODEL (default gpt-4o-mini), temp 0eval_lib) per requestPOST /askCapabilities
- Extract listings from a page →
{title, price, currency, url}(LLM-based semantic parsing). - Filter listings by
min_price,max_price, and/or a substring in the title. - Normalize currency to
RUB,USD, orEUR(static rates). - Compute statistics over prices →
{min, max, avg, median}. - Compare multiple pages and compile a brief report with 3–5 example links.
- Tag runs with a session id and user id for grouping and analytics.
Business requirements
Functional requirements
| ID | Requirement |
|---|---|
FR-1 | Given one or more public URLs, extract structured listings (title, price as integer, currency, url). |
FR-2 | Filter listings by minimum/maximum price and/or a title keyword. |
FR-3 | Convert listing prices to a requested target currency (RUB / USD / EUR). |
FR-4 | Compute descriptive price statistics: min, max, average, median. |
FR-5 | Support multi-page comparison in a single request. |
FR-6 | Interpret a natural-language question and autonomously select and chain the correct tools. |
FR-7 | Return a concise report; include 3–5 example links; state explicitly when no data is available. |
FR-8 | Group related requests by session id and attribute them to a user id. |
Non-functional requirements
| ID | Requirement |
|---|---|
NFR-1 | All API calls authenticated via an X-API-Key header. |
NFR-2 | Every request fully traced (LLM calls, tool calls, tokens, cost) via Langfuse and EvaliQA. |
NFR-3 | Deterministic-leaning behavior (temperature 0) for reproducible evaluation. |
NFR-4 | Works on publicly accessible, server-rendered pages only (no auth, no JS-rendered content). |
NFR-5 | Prefer tool results over model prior knowledge for any price or listing. |
Evaluation dimensions
Suggested axes for a comprehensive assessment. Send inputs to POST /ask and score the output:
| Dimension | What to check |
|---|---|
| Extraction accuracy | Precision/recall of listings; correct price parsing (integer, no separators) and currency detection. |
| Tool selection | Right tools chosen in the right order; check tools_used against expectation. |
| Numeric correctness | Stats (min/max/avg/median) and currency conversions are arithmetically correct. |
| Filtering correctness | Price bounds respected (note: bounds are inclusive); keyword match is case-insensitive. |
| Grounding / faithfulness | No hallucinated listings or prices; numbers trace back to tool output. |
| Robustness | Graceful handling of bad/unreachable URLs, empty pages, and non-listing pages. |
| Instruction following | Brevity; 3–5 example links; explicit "no data" when appropriate. |
| Limits | max_items respected during extraction. |
| Latency & cost | End-to-end time and token cost (available in traces). |
Limitations & caveats
- Shared "last offers" state. The most recently extracted listings are stored in a single process-global variable, not keyed by session id. A follow-up question that omits
urlsoperates on whatever was extracted last across all sessions. For isolated evaluation, include theurlsin each request or run requests serially. - No conversation memory. Each
/askcall is independent — there is no chat history. Multi-turn behavior relies only on the shared "last offers" state above. - Static FX rates. Currency conversion uses hardcoded rates and can be stale.
- HTML truncation. Pages are truncated to
MAX_HTML_CHARS(~160k chars) before parsing; long pages may lose listings. - Extraction is LLM-dependent. Quality varies with page structure and the model; dynamic (JS-rendered) sites usually yield nothing.
- Public pages only. No authentication, cookies, or paywalled content.
Base URL & authentication
…X-API-Key: <key> — required everywhere| Header | Required | Purpose |
|---|---|---|
X-API-Key | required | Authentication. A wrong or missing value returns 401. |
X-Session-Id | optional | Groups related requests. If omitted, the server generates one and returns it as _session_id. |
X-User-Id | optional | Attributes the request to a user (echoed back as _user_id). |
POST /ask
The primary endpoint — exactly where the chat UI sends data. It runs the full agent (extraction → filter → normalize → stats, as needed) and returns a natural-language answer plus the tools it used.
Request headers
Content-Type: application/json X-API-Key: 80456142-5441-4469-b97f-1d72b7802a93 X-Session-Id: 123e4567-e89b-12d3-a456-426614174000 # optional X-User-Id: user123 # optional
Request body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
question | string | yes | — | The natural-language task. |
urls | string[] | no | [] | Listing pages to extract from. |
max_items | integer | no | 50 | Max listings per page. |
{
"question": "Find apartments cheaper than 2000 dollars and show price statistics",
"urls": ["https://www.rentalads.com/apartments-for-rent/ny/new-york/"],
"max_items": 50
}
Response 200 OK
| Field | Type | Description |
|---|---|---|
output | string | The agent's natural-language answer. |
tools_used | string[] | Tools invoked, in call order. Useful for asserting behavior. |
_session_id | string | Session id used (from header, or newly generated). |
_user_id | string | Present only if X-User-Id was sent. |
{
"output": "Found 3 apartments under $2000. Cheapest is a studio at $1500...",
"tools_used": ["extract_offers", "filter_offers", "compute_stats"],
"_session_id": "123e4567-e89b-12d3-a456-426614174000",
"_user_id": "user123"
}
urls — the agent reuses the last extracted listings. See the shared-state caveat under Limitations.
Agent tools
The agent may call any of these. Their names appear in the tools_used response field.
url (required), limit (default 50). Returns [{title, price, currency, url}] and stores them as the "last offers".offers (optional — defaults to last offers), min_price, max_price (inclusive), text_contains (case-insensitive title substring).offers (optional), target_currency (RUB|USD|EUR, default RUB).{min, max, avg, median}. Args: prices (int[]) or offers; if both omitted, uses last offers.Other endpoints
GET /health — liveness check. Requires X-API-Key. Returns {"status": "ok"}.
GET /session/new — generate a session id to reuse in X-Session-Id. Returns {"session_id": "…", "message": "…"}.
POST /monitor — direct extraction, bypassing the agent. Body {"url": "…", "max_items": 50} → {"url": "…", "offers": [...]}. Useful as ground truth for extraction-accuracy evaluation.
Errors
| Status | When | Body |
|---|---|---|
| 401 | Missing/invalid X-API-Key | {"detail": "Invalid API key"} |
| 422 | Malformed body (missing question, wrong type) | Pydantic validation error |
| 500 | Scraper/network/LLM failure | Server error |
Connect from Postman
- In Postman: Import → Raw text, paste the collection below, and import.
- Open the collection's Variables tab and confirm
base_urlandapi_key. - Run POST /ask. Edit the body to send your evaluation inputs.
…
{{session_id}} collection variable in X-Session-Id to keep a run's requests grouped, or call GET /session/new first and paste the returned id.
cURL
…