Skip to content

Commit e50bd15

Browse files
committed
complete refactor during filming
1 parent cf2462e commit e50bd15

280 files changed

Lines changed: 11087 additions & 3977 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

module-01-reproducibility/demos/realthor_v01.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@
510510
],
511511
"metadata": {
512512
"kernelspec": {
513-
"display_name": "agents",
513+
"display_name": "agentops (3.12.11)",
514514
"language": "python",
515515
"name": "python3"
516516
},

module-02-evals/demos/.env.example

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1+
# LLM provider: "vocareum" (Udacity workspace default) or "openai".
2+
LLM_PROVIDER=vocareum
3+
4+
# Vocareum key, used when LLM_PROVIDER=vocareum. Udacity provides this in the workspace.
5+
VOCAREUM_API_KEY=voc-...
6+
7+
# OpenAI key, used when LLM_PROVIDER=openai (run against OpenAI directly).
18
OPENAI_API_KEY=sk-...

module-03-deploy/demos/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# RealThor v0.3 — Demo Project
2+
3+
One demo video, two parts. Part 1 shows how to deploy RealThor as a CLI package. Part 2 shows RealThor as a LangChain agent with tools.
4+
5+
## Setup
6+
7+
```bash
8+
cp .env.example .env # add your OPENAI_API_KEY
9+
uv sync
10+
```
11+
12+
## Project Structure
13+
14+
```
15+
demos/
16+
├── realthor_v02.ipynb ← the "before": prior version, run as a notebook
17+
├── pyproject.toml ← build-system, deps, CLI entry point
18+
├── config.yaml ← model name, temperature, file paths
19+
├── data/
20+
│ ├── demand_signals.csv
21+
│ └── supply_snapshot.csv
22+
├── prompts/
23+
│ └── system_prompt_v3.md
24+
└── src/
25+
└── realthor/
26+
├── config.py ← load_config, resolve_paths, build_context
27+
├── tools.py ← make_tools(demand, supply) → tool closures
28+
├── cli.py ← Part 1: lookup command, no model
29+
└── agent.py ← Part 2: build_agent + __main__ block
30+
```
31+
32+
## Part 1 — CLI with arguments and flags
33+
34+
```bash
35+
uv run realthor --help
36+
uv run realthor lookup --region west --property-type apartment --bedrooms 2
37+
uv run realthor lookup --region south --property-type house --bedrooms 3
38+
```
39+
40+
No model call. Reads CSVs, prints demand and supply data for the segment.
41+
42+
## Part 2 — RealThor as an agent
43+
44+
```bash
45+
uv run python src/realthor/agent.py
46+
```
47+
48+
The agent calls `get_demand_signals` and `get_supply_snapshot` tools, then reasons over the results using the system prompt decision framework.
49+
50+
To change the question, edit the `HumanMessage` in the `__main__` block of `src/realthor/agent.py`.
51+
52+
## Config
53+
54+
`config.yaml` controls the model and all file paths. No values are hardcoded in Python.
55+
56+
```yaml
57+
model:
58+
name: gpt-4o-mini
59+
temperature: 0.0
60+
61+
paths:
62+
prompt: prompts/system_prompt_v3.md
63+
demand_data: data/demand_signals.csv
64+
supply_data: data/supply_snapshot.csv
65+
```

module-03-deploy/demos/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ description = "RealThor — Listing Supply Intelligence agent for Ciudaty PropTe
1212
requires-python = ">=3.12"
1313
dependencies = [
1414
"click>=8.0.0",
15+
"ipykernel>=7.3.0",
1516
"langchain>=1.3.0",
1617
"langchain-openai>=1.2.1",
1718
"langgraph>=1.2.0",
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# RealThor v0.2 — Structured Output with LangChain\n",
8+
"\n",
9+
"In v0.1, RealThor returned free-form text. That worked for reading, but made evaluation brittle, we had to parse strings and match phrases that the model might phrase differently every run.\n",
10+
"\n",
11+
"v0.2 upgrades to **structured output**: instead of a paragraph, RealThor returns a validated Pydantic object with typed fields. Evaluation becomes exact field comparison, no string parsing."
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": 1,
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"import os\n",
21+
"import pandas as pd\n",
22+
"from typing import Literal\n",
23+
"from pydantic import BaseModel\n",
24+
"from dotenv import load_dotenv\n",
25+
"from langchain_openai import ChatOpenAI\n",
26+
"from langchain_core.messages import SystemMessage, HumanMessage"
27+
]
28+
},
29+
{
30+
"cell_type": "code",
31+
"execution_count": 2,
32+
"metadata": {},
33+
"outputs": [
34+
{
35+
"data": {
36+
"text/plain": [
37+
"True"
38+
]
39+
},
40+
"execution_count": 2,
41+
"metadata": {},
42+
"output_type": "execute_result"
43+
}
44+
],
45+
"source": [
46+
"load_dotenv()"
47+
]
48+
},
49+
{
50+
"cell_type": "code",
51+
"execution_count": 5,
52+
"metadata": {},
53+
"outputs": [],
54+
"source": [
55+
"with open(\"prompts/system_prompt_v3.md\") as f:\n",
56+
" SYSTEM_PROMPT = f.read()"
57+
]
58+
},
59+
{
60+
"cell_type": "code",
61+
"execution_count": 6,
62+
"metadata": {},
63+
"outputs": [],
64+
"source": [
65+
"demand = pd.read_csv(\"data/demand_signals.csv\")"
66+
]
67+
},
68+
{
69+
"cell_type": "code",
70+
"execution_count": 7,
71+
"metadata": {},
72+
"outputs": [],
73+
"source": [
74+
"supply = pd.read_csv(\"data/supply_snapshot.csv\")"
75+
]
76+
},
77+
{
78+
"cell_type": "markdown",
79+
"metadata": {},
80+
"source": [
81+
"## Define the Output Schema\n",
82+
"\n",
83+
"This Pydantic model mirrors the output fields in `system_prompt_v2.md`. The model is forced to return values that match these types — no free-form text."
84+
]
85+
},
86+
{
87+
"cell_type": "code",
88+
"execution_count": 8,
89+
"metadata": {},
90+
"outputs": [],
91+
"source": [
92+
"class ListingRecommendation(BaseModel):\n",
93+
" \"\"\"Structured recommendation from RealThor.\"\"\"\n",
94+
" priority: Literal[\"high\", \"medium\", \"low\"]\n",
95+
" demand_reasoning: str\n",
96+
" supply_reasoning: str\n",
97+
" recommended_action: Literal[\"prioritize_new_listings\", \"monitor_opportunity\", \"deprioritize\"]\n",
98+
" confidence: Literal[\"high\", \"medium\", \"low\"]"
99+
]
100+
},
101+
{
102+
"cell_type": "markdown",
103+
"metadata": {},
104+
"source": [
105+
"## Create the Structured LLM\n",
106+
"\n",
107+
"`with_structured_output()` wraps the model so every response is parsed and validated against `ListingRecommendation`. If the model produces an invalid value, LangChain retries automatically."
108+
]
109+
},
110+
{
111+
"cell_type": "code",
112+
"execution_count": 9,
113+
"metadata": {},
114+
"outputs": [],
115+
"source": [
116+
"provider = os.getenv(\"LLM_PROVIDER\", \"openai\")\n",
117+
"model_kwargs = {\"model\": \"gpt-4o-mini\", \"temperature\": 0.2}\n",
118+
"if provider == \"vocareum\":\n",
119+
" model_kwargs |= {\"base_url\": \"https://openai.vocareum.com/v1\", \"api_key\": os.getenv(\"VOCAREUM_API_KEY\")}\n",
120+
"llm = ChatOpenAI(**model_kwargs)"
121+
]
122+
},
123+
{
124+
"cell_type": "code",
125+
"execution_count": 10,
126+
"metadata": {},
127+
"outputs": [],
128+
"source": [
129+
"structured_llm = llm.with_structured_output(ListingRecommendation)"
130+
]
131+
},
132+
{
133+
"cell_type": "markdown",
134+
"metadata": {},
135+
"source": [
136+
"## Helper Functions"
137+
]
138+
},
139+
{
140+
"cell_type": "code",
141+
"execution_count": 11,
142+
"metadata": {},
143+
"outputs": [],
144+
"source": [
145+
"def build_context(region: str, property_type: str, bedrooms: int) -> str:\n",
146+
" d = demand[\n",
147+
" (demand[\"region\"] == region)\n",
148+
" & (demand[\"property_type\"] == property_type)\n",
149+
" & (demand[\"bedrooms\"] == bedrooms)\n",
150+
" ]\n",
151+
" s = supply[\n",
152+
" (supply[\"region\"] == region)\n",
153+
" & (supply[\"property_type\"] == property_type)\n",
154+
" & (supply[\"bedrooms\"] == bedrooms)\n",
155+
" ]\n",
156+
" if d.empty or s.empty:\n",
157+
" return \"No data available for the requested segment.\"\n",
158+
" return (\n",
159+
" f\"Region: {region}\\n\"\n",
160+
" f\"Property: {bedrooms}-bedroom {property_type}\\n\\n\"\n",
161+
" f\"Demand signals:\\n{d.to_string(index=False)}\\n\\n\"\n",
162+
" f\"Supply snapshot:\\n{s.to_string(index=False)}\"\n",
163+
" )"
164+
]
165+
},
166+
{
167+
"cell_type": "code",
168+
"execution_count": 12,
169+
"metadata": {},
170+
"outputs": [],
171+
"source": [
172+
"def ask_realthor_v2(region: str, property_type: str, bedrooms: int) -> ListingRecommendation:\n",
173+
" context = build_context(region, property_type, bedrooms)\n",
174+
" return structured_llm.invoke([\n",
175+
" SystemMessage(content=SYSTEM_PROMPT),\n",
176+
" HumanMessage(content=context),\n",
177+
" ])"
178+
]
179+
},
180+
{
181+
"cell_type": "markdown",
182+
"metadata": {},
183+
"source": [
184+
"## Run One Example"
185+
]
186+
},
187+
{
188+
"cell_type": "code",
189+
"execution_count": 13,
190+
"metadata": {},
191+
"outputs": [],
192+
"source": [
193+
"result = ask_realthor_v2(\"west\", \"apartment\", 2)"
194+
]
195+
},
196+
{
197+
"cell_type": "code",
198+
"execution_count": 14,
199+
"metadata": {},
200+
"outputs": [
201+
{
202+
"name": "stdout",
203+
"output_type": "stream",
204+
"text": [
205+
"Priority: high\n",
206+
"Recommended action: prioritize_new_listings\n",
207+
"Confidence: high\n",
208+
"\n",
209+
"Demand reasoning: The demand score of 0.91 indicates a very high level of interest in 2-bedroom apartments in the west region, with significant search volume and lead count suggesting strong buyer intent.\n",
210+
"Supply reasoning: With only 18 active listings and a supply score of 0.22, the market is experiencing constrained supply, meaning there are not enough available properties to meet the high demand.\n"
211+
]
212+
}
213+
],
214+
"source": [
215+
"print(f\"Priority: {result.priority}\")\n",
216+
"print(f\"Recommended action: {result.recommended_action}\")\n",
217+
"print(f\"Confidence: {result.confidence}\")\n",
218+
"print(f\"\\nDemand reasoning: {result.demand_reasoning}\")\n",
219+
"print(f\"Supply reasoning: {result.supply_reasoning}\")"
220+
]
221+
},
222+
{
223+
"cell_type": "markdown",
224+
"metadata": {},
225+
"source": [
226+
"## What Changed\n",
227+
"\n",
228+
"| | v0.1 | v0.2 |\n",
229+
"|---|---|---|\n",
230+
"| Output | Free-form string | Pydantic object |\n",
231+
"| Priority check | `\"high\" in output.lower()` | `result.priority == \"high\"` |\n",
232+
"| Action check | String search | `result.recommended_action == \"prioritize_new_listings\"` |\n",
233+
"| Invalid values | Model can say anything | LangChain/Pydantic validates and retries |\n",
234+
"| Evaluation | Brittle | More deterministic |\n",
235+
"\n",
236+
"From this point forward, all evaluation uses `result.priority` and `result.recommended_action` — exact field comparisons against the golden dataset."
237+
]
238+
},
239+
{
240+
"cell_type": "markdown",
241+
"id": "4ca1d273",
242+
"metadata": {},
243+
"source": []
244+
}
245+
],
246+
"metadata": {
247+
"kernelspec": {
248+
"display_name": "realthor (3.12.11)",
249+
"language": "python",
250+
"name": "python3"
251+
},
252+
"language_info": {
253+
"codemirror_mode": {
254+
"name": "ipython",
255+
"version": 3
256+
},
257+
"file_extension": ".py",
258+
"mimetype": "text/x-python",
259+
"name": "python",
260+
"nbconvert_exporter": "python",
261+
"pygments_lexer": "ipython3",
262+
"version": "3.12.11"
263+
}
264+
},
265+
"nbformat": 4,
266+
"nbformat_minor": 5
267+
}

0 commit comments

Comments
 (0)