🐍 LangChain · Gemini 2.5 Flash · AI: Principles and Application · 4V98

HandyMan AI Booking Agent
powered by LangChain

A Python-native LangChain agent backed by Gemini 2.5 Flash with three live tools — it reads a Google Sheet for dynamic pricing, checks Google Calendar for real-time availability, and books confirmed appointments directly to the calendar. All in a single conversational loop.

3Live Tools
3Google APIs
ReActAgent Type
0Hallucinated Prices

Business Problem

Handyman businesses waste time manually quoting and scheduling every job. This agent handles the full intake loop in conversation — pricing a job from live sheet data, finding open calendar slots, and booking confirmed appointments — so the business owner never has to touch a form.

One-Sentence Summary

A LangChain conversational agent with Gemini 2.5 Flash that quotes handyman jobs from Google Sheets, checks live calendar availability, and creates confirmed Google Calendar appointments — all through natural language.

My Role

Built every component from scratch: designed and implemented all three @tool functions, wrote the system prompt, configured the ReAct agent loop, handled Google OAuth, and wired the full conversation state management.

Biggest Challenge

Getting the agent to confirm before booking — LangChain agents are eager and will call tools immediately. The solution was a carefully engineered system prompt that teaches the agent to present slots as numbered options and wait for explicit user confirmation before invoking create_calendar_event_tool.

What I Learned

System prompt design is half the work with tool-calling agents. The tool definitions (docstrings) and the system prompt are the only things standing between a helpful agent and one that books appointments without asking. Precise language matters enormously.

Tools Used

Python LangChain Gemini 2.5 Flash Google Sheets API Google Calendar API OAuth 2.0 dotenv

Key Features

ReAct agent loop with Gemini 2.5 Flash as the reasoning backbone. Three live @tool functions: dynamic pricing from Google Sheets, real-time availability from Google Calendar, and confirmed booking creation via Calendar API. Full OAuth 2.0 credential flow. System prompt engineered to force slot-confirmation before any booking action. Conversation state maintained across the full session.

Click any tool to see its implementation

💰
pricing_tool
Google Sheets → Gemini
🤖
LangChain Agent
Gemini 2.5 Flash
ReAct loop
📅
scheduling_tool
Google Calendar freebusy
create_calendar_event_tool
Calendar write — on confirm only

👆 Click a tool above

Each @tool function has a docstring that tells the LangChain agent exactly when to use it. The agent decides which tool to call based on the user's message — no hardcoded routing.

Try the simulated agent conversation

🔧
HandyMan Assistant
● Powered by Gemini 2.5 Flash · LangChain
🔧
Hi! I'm your HandyMan booking assistant. I can give you a quote on any job, check availability, and book your appointment. What do you need help with?

This is a simulated demo — try: "how much for drywall repair", "what times are free Friday afternoon", or "book me for Tuesday at 10am"

Key files from the project

main.py
mypricing_tool.py
myscheduling_tool.py
mycalendar_booking_tool.py
from langchain.agents import create_agent
from langchain_google_genai import ChatGoogleGenerativeAI
from mypricing_tool import pricing_tool
from myscheduling_tool import scheduling_tool
from mycalendar_booking_tool import create_calendar_event_tool

model = ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0)
tools = [pricing_tool, scheduling_tool, create_calendar_event_tool]

agent = create_agent(
    model=model,
    tools=tools,
    system_prompt="""
You are a handyman service assistant.
Use pricing_tool when the user asks for a quote or estimate.
Use scheduling_tool when the user asks for appointment availability.
Use create_calendar_event_tool ONLY after the user clearly confirms a slot.
Present slots as numbered options. Do not book without explicit confirmation.
"""
)

def main():
    conversation = []
    while True:
        user_input = input('USER >> ')
        if user_input.lower() in ["quit", "exit"]: break
        conversation.append({"role": "user", "content": user_input})
        response = agent.invoke({"messages": conversation})
        conversation.extend(response["messages"])
@tool
def pricing_tool(client_request: str):
    """
    Review the customer's request and business pricing sheet,
    then determine the best quote.
    Use this when the user asks for a quote, estimate, or pricing.
    """
    # Step 1: Read live pricing from Google Sheets
    sheets_service = get_sheets_service()
    result = sheets_service.spreadsheets().values().get(
        spreadsheetId=SPREADSHEET_ID,
        range="PricingSheet!A1:G100"
    ).execute()
    pricing_records = build_records(result)

    # Step 2: Ask Gemini to interpret and quote
    prompt = f"""
You are a pricing analyst. Given this request and pricing sheet,
return JSON with: service_type, urgency, complexity, estimated_price, explanation.

Customer: {client_request}
Pricing: {json.dumps(pricing_records)}
"""
    response = model.invoke(prompt)
    return json.loads(response.content)
@tool
def scheduling_tool(requested_day: str, time_window: str = "afternoon", duration_minutes: int = 60):
    """Find available Google Calendar slots for the requested day/time window."""
    target_date = parse_next_weekday(requested_day)
    start_dt, end_dt = get_window(target_date, time_window)

    # Query Google Calendar freebusy API
    busy = get_busy_periods(calendar_service, start_dt, end_dt)
    open_slots = compute_open_slots(start_dt, end_dt, busy, duration_minutes)

    return {
        "available_slots": [
            {"option_number": i+1,
             "display": slot.strftime("%A %I:%M %p"),
             "start_iso": slot.isoformat()}
            for i, slot in enumerate(open_slots[:5])
        ]
    }
@tool
def create_calendar_event_tool(
    customer_name: str, service_type: str,
    start_iso: str, duration_minutes: int = 60,
    location: str = "", notes: str = ""):
    """
    Create a Google Calendar event for a CONFIRMED appointment.
    Only call this AFTER the user has explicitly confirmed a time slot.
    start_iso must be a full ISO datetime string.
    """
    start_dt = datetime.fromisoformat(start_iso)
    end_dt   = start_dt + timedelta(minutes=duration_minutes)

    event = {
        "summary":  f"Handyman Appointment - {customer_name}",
        "start":    {"dateTime": start_dt.isoformat(), "timeZone": "America/Chicago"},
        "end":      {"dateTime": end_dt.isoformat(), "timeZone": "America/Chicago"},
        "description": f"Service: {service_type}\nNotes: {notes}"
    }
    created = calendar_service.events().insert(calendarId="primary", body=event).execute()
    return {"status": "success", "event_link": created["htmlLink"]}