Housing Price Agent — Guide & API
agent overview · business requirements · API reference for evaluation
Open chat

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.

StackLangChain OpenAI-tools agent · FastAPI · BeautifulSoup
ModelOPENAI_MODEL (default gpt-4o-mini), temp 0
ObservabilityLangfuse + EvaliQA (eval_lib) per request
Primary endpointPOST /ask

Capabilities

  • 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, or EUR (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

IDRequirement
FR-1Given one or more public URLs, extract structured listings (title, price as integer, currency, url).
FR-2Filter listings by minimum/maximum price and/or a title keyword.
FR-3Convert listing prices to a requested target currency (RUB / USD / EUR).
FR-4Compute descriptive price statistics: min, max, average, median.
FR-5Support multi-page comparison in a single request.
FR-6Interpret a natural-language question and autonomously select and chain the correct tools.
FR-7Return a concise report; include 3–5 example links; state explicitly when no data is available.
FR-8Group related requests by session id and attribute them to a user id.

Non-functional requirements

IDRequirement
NFR-1All API calls authenticated via an X-API-Key header.
NFR-2Every request fully traced (LLM calls, tool calls, tokens, cost) via Langfuse and EvaliQA.
NFR-3Deterministic-leaning behavior (temperature 0) for reproducible evaluation.
NFR-4Works on publicly accessible, server-rendered pages only (no auth, no JS-rendered content).
NFR-5Prefer 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:

DimensionWhat to check
Extraction accuracyPrecision/recall of listings; correct price parsing (integer, no separators) and currency detection.
Tool selectionRight tools chosen in the right order; check tools_used against expectation.
Numeric correctnessStats (min/max/avg/median) and currency conversions are arithmetically correct.
Filtering correctnessPrice bounds respected (note: bounds are inclusive); keyword match is case-insensitive.
Grounding / faithfulnessNo hallucinated listings or prices; numbers trace back to tool output.
RobustnessGraceful handling of bad/unreachable URLs, empty pages, and non-listing pages.
Instruction followingBrevity; 3–5 example links; explicit "no data" when appropriate.
Limitsmax_items respected during extraction.
Latency & costEnd-to-end time and token cost (available in traces).

Limitations & caveats

These reflect the current implementation and directly affect how you should design test cases.
  • 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 urls operates on whatever was extracted last across all sessions. For isolated evaluation, include the urls in each request or run requests serially.
  • No conversation memory. Each /ask call 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

Base URL (this instance)
Auth headerX-API-Key: <key> — required everywhere
HeaderRequiredPurpose
X-API-KeyrequiredAuthentication. A wrong or missing value returns 401.
X-Session-IdoptionalGroups related requests. If omitted, the server generates one and returns it as _session_id.
X-User-IdoptionalAttributes 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

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

FieldTypeRequiredDefaultDescription
questionstringyesThe natural-language task.
urlsstring[]no[]Listing pages to extract from.
max_itemsintegerno50Max listings per page.
json — request body
{
  "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

FieldTypeDescription
outputstringThe agent's natural-language answer.
tools_usedstring[]Tools invoked, in call order. Useful for asserting behavior.
_session_idstringSession id used (from header, or newly generated).
_user_idstringPresent only if X-User-Id was sent.
json — response
{
  "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"
}
For follow-up questions (e.g. "now convert prices to euros"), you may omit 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.

extract_offers
Extract listings from a page. Args: url (required), limit (default 50). Returns [{title, price, currency, url}] and stores them as the "last offers".
filter_offers
Filter listings. Args: offers (optional — defaults to last offers), min_price, max_price (inclusive), text_contains (case-insensitive title substring).
normalize_offers_currency
Convert prices to a target currency. Args: offers (optional), target_currency (RUB|USD|EUR, default RUB).
compute_stats
Compute {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.

FastAPI also serves interactive OpenAPI docs at /docs (Swagger) and /redoc.

Errors

StatusWhenBody
401Missing/invalid X-API-Key{"detail": "Invalid API key"}
422Malformed body (missing question, wrong type)Pydantic validation error
500Scraper/network/LLM failureServer error

Connect from Postman

  1. In Postman: Import → Raw text, paste the collection below, and import.
  2. Open the collection's Variables tab and confirm base_url and api_key.
  3. Run POST /ask. Edit the body to send your evaluation inputs.
json — Postman collection (v2.1)
Use the {{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

bash