---
title: "Building an HVAC Service Agent: Troubleshooting Guides, Scheduling, and Part Ordering"
description: "Learn how to build an AI agent for HVAC service companies that walks technicians and customers through diagnostic trees, books appointments, looks up parts, and generates quotes automatically."
canonical: https://callsphere.ai/blog/building-hvac-service-agent-troubleshooting-scheduling-parts
category: "Learn Agentic AI"
tags: ["HVAC", "Field Service AI", "Troubleshooting", "Scheduling", "Parts Management"]
author: "CallSphere Team"
published: 2026-03-17T00:00:00.000Z
updated: 2026-05-06T01:02:44.681Z
---

# Building an HVAC Service Agent: Troubleshooting Guides, Scheduling, and Part Ordering

> Learn how to build an AI agent for HVAC service companies that walks technicians and customers through diagnostic trees, books appointments, looks up parts, and generates quotes automatically.

## Why HVAC Companies Need AI Agents

HVAC service companies handle hundreds of calls daily — from emergency no-heat situations to routine filter replacements. Each call requires triaging the problem, checking technician availability, looking up compatible parts, and generating accurate quotes. An AI agent can handle this entire workflow, reducing dispatcher workload by 60-70% while ensuring consistent, accurate service.

The key challenge is building a diagnostic engine that mirrors how experienced HVAC technicians think. A furnace that will not ignite could be a dirty flame sensor, a faulty ignitor, a gas valve issue, or a control board failure. The agent must ask the right questions in the right order to narrow down the problem before dispatching a technician with the correct parts.

## Designing the Diagnostic Tree

HVAC diagnostics follow well-established decision trees. We model these as structured data that the agent traverses based on customer responses.

```mermaid
flowchart LR
    CALLER(["Homeowner"])
    subgraph TEL["Telephony"]
        SIP["Twilio SIP and PSTN"]
    end
    subgraph BRAIN["Field Service AI Agent"]
        STT["Streaming STT
Deepgram or Whisper"]
        NLU{"Intent and
Entity Extraction"}
        TOOLS["Tool Calls"]
        TTS["Streaming TTS
ElevenLabs or Rime"]
    end
    subgraph DATA["Live Data Plane"]
        CRM[("CRM and Notes")]
        CAL[("Calendar and
Schedule")]
        KB[("Knowledge Base
and Policies")]
    end
    subgraph OUT["Outcomes"]
        O1(["Service appointment booked"])
        O2(["Quote sent via SMS"])
        O3(["Tech dispatched today"])
    end
    CALLER --> SIP --> STT --> NLU
    NLU -->|Lookup| TOOLS
    TOOLS  CRM
    TOOLS  CAL
    TOOLS  KB
    NLU --> TTS --> SIP --> CALLER
    NLU -->|Resolved| O1
    NLU -->|Schedule| O2
    NLU -->|Escalate| O3
    style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
    style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
    style O1 fill:#059669,stroke:#047857,color:#fff
    style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
    style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
```

```python
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class DiagnosticNode:
    node_id: str
    question: str
    options: dict[str, str]  # answer -> next_node_id
    diagnosis: Optional[str] = None
    required_parts: list[str] = field(default_factory=list)
    urgency: str = "standard"  # standard, same_day, emergency

FURNACE_DIAGNOSTIC_TREE = {
    "start": DiagnosticNode(
        node_id="start",
        question="Is the furnace producing any heat at all?",
        options={"no_heat": "check_thermostat", "weak_heat": "check_filter"},
    ),
    "check_thermostat": DiagnosticNode(
        node_id="check_thermostat",
        question="Is your thermostat set to HEAT mode and set above current room temperature?",
        options={"yes": "check_ignition", "no": "thermostat_fix"},
    ),
    "thermostat_fix": DiagnosticNode(
        node_id="thermostat_fix",
        question=None,
        options={},
        diagnosis="Thermostat misconfiguration. Adjust settings.",
        urgency="standard",
    ),
    "check_ignition": DiagnosticNode(
        node_id="check_ignition",
        question="Do you hear the furnace clicking or attempting to start?",
        options={"yes": "flame_sensor_issue", "no": "control_board_issue"},
    ),
    "flame_sensor_issue": DiagnosticNode(
        node_id="flame_sensor_issue",
        question=None,
        options={},
        diagnosis="Likely dirty or failed flame sensor. Technician visit required.",
        required_parts=["flame_sensor", "ignitor_backup"],
        urgency="same_day",
    ),
}
```

## Building the Parts Lookup System

Each diagnosis maps to specific parts. The agent needs to check inventory and pricing in real time.

```python
from datetime import datetime

class PartsInventory:
    def __init__(self, db_connection):
        self.db = db_connection

    async def lookup_parts(
        self, part_codes: list[str], equipment_model: str
    ) -> list[dict]:
        query = """
            SELECT p.part_number, p.description, p.price,
                   i.quantity_on_hand, p.supplier_lead_days,
                   c.model_numbers
            FROM parts p
            JOIN inventory i ON p.part_id = i.part_id
            JOIN compatibility c ON p.part_id = c.part_id
            WHERE p.category_code = ANY($1)
              AND $2 = ANY(c.model_numbers)
            ORDER BY i.quantity_on_hand DESC
        """
        rows = await self.db.fetch(query, part_codes, equipment_model)
        return [
            {
                "part_number": r["part_number"],
                "description": r["description"],
                "price": float(r["price"]),
                "in_stock": r["quantity_on_hand"] > 0,
                "available_date": (
                    datetime.now().strftime("%Y-%m-%d")
                    if r["quantity_on_hand"] > 0
                    else f"{r['supplier_lead_days']} business days"
                ),
            }
            for r in rows
        ]

    async def generate_quote(
        self, parts: list[dict], labor_hours: float, urgency: str
    ) -> dict:
        parts_total = sum(p["price"] for p in parts)
        labor_rate = {"standard": 95, "same_day": 135, "emergency": 185}
        labor_cost = labor_hours * labor_rate.get(urgency, 95)
        return {
            "parts_total": round(parts_total, 2),
            "labor_estimate": round(labor_cost, 2),
            "total_estimate": round(parts_total + labor_cost, 2),
            "urgency": urgency,
            "valid_until": "48 hours",
        }
```

## Scheduling Integration

The agent checks technician availability and books appointments based on urgency and skill requirements.

```python
from datetime import datetime, timedelta

class HVACScheduler:
    def __init__(self, calendar_service):
        self.calendar = calendar_service

    async def find_available_slots(
        self, urgency: str, skill_required: str, zip_code: str
    ) -> list[dict]:
        if urgency == "emergency":
            window_start = datetime.now()
            window_end = window_start + timedelta(hours=4)
        elif urgency == "same_day":
            window_start = datetime.now()
            window_end = window_start.replace(hour=18, minute=0)
        else:
            window_start = datetime.now() + timedelta(days=1)
            window_end = window_start + timedelta(days=5)

        technicians = await self.calendar.get_qualified_techs(
            skill=skill_required, service_area=zip_code
        )
        slots = []
        for tech in technicians:
            available = await self.calendar.get_open_slots(
                tech_id=tech["id"],
                start=window_start,
                end=window_end,
            )
            for slot in available:
                slots.append({
                    "technician": tech["name"],
                    "date": slot["date"],
                    "time_window": slot["window"],
                    "estimated_arrival": slot["eta"],
                })
        return sorted(slots, key=lambda s: s["date"])
```

## Wiring It All Together as an Agent

The complete agent orchestrates diagnostics, parts lookup, quoting, and scheduling into a single conversational flow.

```python
from agents import Agent, Runner, function_tool

@function_tool
async def diagnose_hvac_issue(symptom: str, responses: dict) -> dict:
    """Walk through the HVAC diagnostic tree based on customer symptoms."""
    node = FURNACE_DIAGNOSTIC_TREE.get("start")
    for answer in responses.values():
        next_id = node.options.get(answer)
        if next_id:
            node = FURNACE_DIAGNOSTIC_TREE.get(next_id, node)
    if node.diagnosis:
        return {
            "diagnosis": node.diagnosis,
            "required_parts": node.required_parts,
            "urgency": node.urgency,
        }
    return {"next_question": node.question, "options": list(node.options.keys())}

hvac_agent = Agent(
    name="HVAC Service Agent",
    instructions="""You are an HVAC service agent. Walk customers through
    diagnostic questions to identify their issue. Once diagnosed, look up
    required parts, generate a quote, and offer available appointment slots.
    Always confirm the equipment model before quoting parts.""",
    tools=[diagnose_hvac_issue],
)
```

## FAQ

### How does the agent handle emergencies like a gas leak?

Gas leaks and carbon monoxide situations bypass the diagnostic tree entirely. The agent is configured with keyword detection for terms like "gas smell," "CO alarm," or "carbon monoxide." When detected, it immediately instructs the customer to leave the building, call 911, and then dispatches an emergency technician without going through the standard flow.

### Can the diagnostic tree handle multiple equipment types?

Yes. You create separate diagnostic trees for furnaces, air conditioners, heat pumps, and boilers, then use the equipment type identified early in the conversation to select the correct tree. The `DiagnosticNode` structure is generic enough to model any branching diagnostic flow.

### How accurate are AI-generated repair quotes?

The quotes are based on real parts pricing from your inventory database and standardized labor times for each repair type. Accuracy typically reaches 85-90% compared to final invoices. The agent presents quotes as estimates and flags when on-site inspection may change the scope.

---

#HVAC #FieldServiceAI #Troubleshooting #Scheduling #PartsManagement #AgenticAI #LearnAI #AIEngineering

---

Source: https://callsphere.ai/blog/building-hvac-service-agent-troubleshooting-scheduling-parts
