We're building an architectural programming agent that reads a plain-text client brief and returns a structured space program with area calculations, occupancy checks, and material suggestions. It runs entirely through Oxlo.ai's request-based API, so a 10-page brief costs the same flat fee as a one-liner. See https://oxlo.ai/pricing for details. If you are an architect or designer automating schematic workflows, this tool drops directly into your Python stack.
What you'll need
- An Oxlo.ai API key from https://portal.oxlo.ai
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai
Step 1: Configure the Oxlo.ai client
One import and two lines. I set the base URL to Oxlo.ai and load the key from an environment variable so I do not commit secrets.
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.getenv("OXLO_API_KEY", "YOUR_OXLO_API_KEY")
)
Step 2: Define the system prompt
The prompt is the contract. It tells the model to output strict JSON with rooms, net/gross areas, occupancy loads per IBC, and proposed materials. I keep it version controlled because changing one line shifts the square footage.
SYSTEM_PROMPT = """You are an architectural programming assistant.
A user will paste a client brief describing a building project.
Respond with a single valid JSON object. Do not wrap it in markdown.
Use this exact structure:
{
"project_type": string,
"total_gross_sf": number,
"rooms": [
{
"name": string,
"net_area_sf": number,
"occupancy_load": number,
"code_remark": string,
"material_palette": [string]
}
],
"summary": string
}
Rules:
- Assume standard ceiling heights unless specified.
- Apply IBC occupancy load factors for business (1/150), assembly (1/15), or residential (1/200) based on context.
- Round areas to the nearest 10 sf.
- If the brief is ambiguous, note the assumption in the code_remark field.
"""
Step 3: Build the generation function
This function takes a brief string, sends it to Oxlo.ai, and parses the JSON response. I use Llama 3.3 70B because it follows structural instructions reliably at a flat per-request price.
BRIEF = """
We need a 4,500 sf community arts center in Portland.
It should include a 1,200 sf gallery, two 400 sf classrooms,
a 300 sf office, restrooms, and a 600 sf lobby with a small cafe.
"""
def generate_space_program(brief: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": brief},
],
temperature=0.2,
max_tokens=2048,
)
raw = response.choices[0].message.content.strip()
# Grab the first '{' to the last '}' in case the model adds chatter
start = raw.find("{")
end = raw.rfind("}") + 1
return json.loads(raw[start:end])
Step 4: Add code compliance
One pass gives us the program, but I want a second sanity check against energy and accessibility heuristics. I send the generated JSON back to the model with a tighter prompt. Because Oxlo.ai charges per request, not per token, this second pass still costs the same flat fee.
COMPLIANCE_PROMPT = """You are a code consultant. Review the provided space program JSON.
Return the same JSON with two added top-level keys:
- "energy_note": a short paragraph on envelope assumptions for the climate.
- "ada_alert": a list of any rooms that likely trigger accessibility requirements.
Do not change existing keys. Output only valid JSON."""
def add_compliance(program: dict) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": COMPLIANCE_PROMPT},
{"role": "user", "content": json.dumps(program, indent=2)},
],
temperature=0.1,
max_tokens=2048,
)
raw = response.choices[0].message.content.strip()
start = raw.find("{")
end = raw.rfind("}") + 1
return json.loads(raw[start:end])
Step 5: Render the report
I wrap the pipeline in a small CLI that prints a readable markdown summary. Architects can pipe this into a file or render it in a notebook.
def render_report(program: dict) -> str:
lines = [
f"# Space Program: {program['project_type']}",
f"**Total Gross Area:** {program['total_gross_sf']:,} sf",
"",
"| Room | Net Area (sf) | Occupancy | Code Remark |",
"|------|---------------|-----------|-------------|",
]
for r in program["rooms"]:
occ = r.get("occupancy_load", "N/A")
lines.append(
f"| {r['name']} | {r['net_area_sf']:,} | {occ} | {r['code_remark']} |"
)
lines.extend([
"",
f"**Energy Note:** {program.get('energy_note', 'N/A')}",
"",
f"**ADA Alerts:** {', '.join(program.get('ada_alert', []))}",
"",
f"**Summary:** {program['summary']}",
])
return "\n".join(lines)
Run it
Call the pipeline from __main__ and print the report.
if __name__ == "__main__":
draft = generate_space_program(BRIEF)
final = add_compliance(draft)
print(render_report(final))
Example output:
# Space Program: Community Arts Center
**Total Gross Area:** 4,520 sf
| Room | Net Area (sf) | Occupancy | Code Remark |
|------|---------------|-----------|-------------|
| Gallery | 1,200 | 80 | Assembly occupancy, IBC 303.1 |
| Classroom A | 400 | 27 | Educational, IBC 305.1 |
| Classroom B | 400 | 27 | Educational, IBC 305.1 |
| Office | 300 | 2 | Business occupancy, IBC 304.1 |
| Lobby / Cafe | 600 | 40 | Assembly / Mercantile mixed |
| Restrooms | 180 | 10 | Plumbing fixture count per IPC |
| Circulation | 440 | 0 | 10% gross added for corridors |
**Energy Note:** Portland Climate Zone 4C suggests a high-performance envelope with R-20 continuous insulation and triple-glazed north-facing gallery windows to mitigate thermal bridging.
**ADA Alerts:** Gallery, Classrooms, Lobby / Cafe, Restrooms
**Summary:** The program packs efficiently into a single-story slab on grade with shared plumbing walls between restrooms and classrooms. Consider clerestory daylighting for the gallery to reduce electrical loads.
Next steps
Connect the agent to a vector store of your local municipality's PDF zoning code and use Oxlo.ai's JSON mode to return cited chapter references instead of generic IBC assumptions. Alternatively, pipe the output into a Rhino or Blender Python script to generate rough massing blocks with the correct net areas extruded to default ceiling heights.
Top comments (0)