6 min read
How function calling enables AI agents: a patient self-scheduling example

Large language models can now use external tools - checking databases, calling APIs, processing payments. This capability, called function calling, transforms a chatbot into an agent that completes multi-step tasks autonomously. I’ve been thinking a lot about medication refill scheduling in the work context lately. Here’s how it would in this context.

The scenario

Today, specialty pharmacy staff make outbound calls to schedule medication refills. Patients often don’t answer, requiring multiple voicemails and callbacks. An AI agent could handle this entire workflow via text:

  1. Text patient that refill is due
  2. Assess if they still have medication on hand
  3. Screen for side effects and escalate to pharmacist if needed
  4. Collect delivery preferences
  5. Calculate earliest available date based on pharmacy dispensing schedule
  6. Process copay
  7. Confirm scheduling

This requires more than conversation. The agent needs to query databases, check calendars, and process payments. That’s where function calling comes in.

How function calling works

The basic flow:

  1. The application send the LLM a prompt + available function definitions (JSON schema)
  2. LLM responds with either text OR “call this function with these arguments”
  3. The code executes the function
  4. Then send the result back to the LLM
  5. LLM uses the result to generate its final response

The LLM doesn’t execute functions directly. It requests that the application execute them. This is akin to giving LLM a toolbox, where it can see what tools are available, decide which one it needs, and ask the app to use it.

The architecture: task by task

Task 1: Initiate contact

Agent goal: Text the patient

Function called:

send_sms(
  phone_number: "+1-555-0123",
  message: "Hi! Time for your medication refill. Reply YES to schedule."
)

Agent receives: {status: "delivered", timestamp: "2025-11-16T10:00:00Z"}

Decision logic: Set 24-hour reminder to follow up if no response.

Task 2: Assess medication status

Patient responds: “Yeah I think I have a few days left”

Function called:

get_medication_history(patient_id: "12345")

Agent receives:

{
  last_fill_date: "2025-10-15",
  days_supply: 30,
  medication: "Lisinopril 10mg"
}

Decision logic:

  • Calculate: Nov 16 - Oct 15 = 32 days elapsed
  • Patient got 30-day supply → likely running low
  • Agent response: “Since you filled on Oct 15 for 30 days, you’re probably running low. Let’s get your refill scheduled for this week.”

Key insight: The agent combines structured data (exact fill date) with ambiguous human input (“a few days”) to make an informed decision.

Task 3: Screen for side effects

Agent: “Before we schedule, how has the medication been working for you? Any side effects or concerns?”

Patient responds: “Actually, I’ve been getting really dizzy lately”

Function called:

check_side_effect_severity(
  medication: "Lisinopril 10mg",
  reported_symptom: "dizziness"
)

Agent receives:

{
  severity: "moderate",
  requires_pharmacist_review: true,
  common_side_effect: true,
  guidance: "Dizziness is a known side effect. Pharmacist consult recommended before refill."
}

Function called:

create_pharmacist_consult(
  patient_id: "12345",
  medication: "Lisinopril 10mg",
  reported_symptom: "dizziness",
  urgency: "routine"
)

Agent receives:

{
  consult_id: "CONSULT-456",
  pharmacist_assigned: "Sarah Chen, PharmD",
  estimated_callback: "within 2 hours"
}

Agent response: “I understand. Dizziness can be a side effect of Lisinopril. I’ve scheduled a call with our pharmacist Sarah within the next 2 hours to discuss this before we process your refill. She’ll call you at this number.”

Decision logic:

  • If requires_pharmacist_review: false → proceed with scheduling
  • If requires_pharmacist_review: true → pause workflow, create consult, notify patient
  • Agent doesn’t make clinical decisions, only routes appropriately

Alternative scenario: If patient reports no side effects, agent would skip pharmacist consult and proceed directly to delivery preferences.

Task 4: Collect delivery preference

Patient: “Can you deliver it?”

Function called:

check_delivery_availability(
  patient_address: "123 Main St, Brooklyn NY 11201"
)

Agent receives:

{
  delivery_available: true,
  delivery_zone: "Zone 2",
  delivery_method: "courier",
  earliest_delivery: "2025-11-18"
}

Agent response: “Yes! We deliver to your area via courier. Earliest delivery is Monday, Nov 18.”

Alternative scenario: If delivery_available: false, agent would call:

get_pickup_locations(zip_code: "11201")

…and offer pharmacy pickup instead.

Task 5: Calculate Earliest Availability

This is where it gets interesting - multiple tools needed:

Function 1:

check_insurance_eligibility(
  patient_id: "12345",
  medication: "Lisinopril 10mg",
  last_fill_date: "2025-10-15"
)

Returns:

{
  eligible: true,
  earliest_refill_date: "2025-11-14",
  copay: 5.00
}

Function 2:

get_pharmacy_availability(
  medication: "Lisinopril 10mg",
  delivery_zone: "Zone 2"
)

Returns:

{
  available_dates: ["2025-11-18", "2025-11-20", "2025-11-22"]
}

Decision logic:

earliest_possible = max(
  insurance_allows: Nov 14,
  pharmacy_can_fulfill: Nov 18,
  patient_requested: ASAP
)
→ Nov 18

Agent response: “Your insurance approves refills starting Nov 14, and we can deliver Monday Nov 18. Does that work?”

Task 6: Process Payment

Patient: “Yes, Monday works”

Function called:

process_copay(
  patient_id: "12345",
  amount: 5.00,
  payment_method_id: "pm_card_on_file"
)

Agent receives:

{
  transaction_id: "txn_abc123",
  status: "succeeded"
}

Agent response: “Perfect! Charged $5 to your card ending in 4242.”

Error handling: If payment fails, agent would call get_payment_methods(patient_id) and ask patient to update card.

Task 7: Confirm Scheduling

Function called:

create_delivery_order(
  patient_id: "12345",
  medication: "Lisinopril 10mg",
  delivery_date: "2025-11-18",
  address: "123 Main St, Brooklyn NY 11201"
)

Agent receives:

{
  order_id: "ORD-789",
  confirmation_code: "MED18X"
}

Final function:

send_sms(
  phone_number: "+1-555-0123",
  message: "Confirmed! Lisinopril delivery on Mon Nov 18. Confirmation: MED18X"
)

Workflow complete.

What Makes This “Agentic”

This is not a simple automation. The agent is doing multiple things:

  1. Makes decisions across tools: Combined insurance data + pharmacy schedule to pick earliest valid date
  2. Handles branching logic: If delivery unavailable → offer pickup; if side effects reported → escalate to pharmacist
  3. Recovers from errors: If payment fails → request new payment method
  4. Maintains context: Remembers patient preference across 7+ function calls
  5. Knows its limits: Doesn’t make clinical decisions, routes medical concerns to qualified professionals

Traditional automation requires pre-mapping every possible path. An agent with function calling adapts in real-time based on what each tool returns.

Why this matters beyond healthcare

This pattern applies anywhere we need AI to interact with business systems:

  • Customer support agents to pull order history, process refunds, update shipping
  • Sales agents to check inventory, generate quotes, schedule demos
  • Real estate agents to text buyers new listing, ask about preferences, confirm property access, send showing confirmation and follow-up

Function calling is what transforms fancy autocomplete into autonomous business process execution.

The technical primitives are ready

Building agents requires clean APIs, structured data, and engineers with bandwidth to integrate tools. The constraint isn’t AI capability, but also organizational readiness, and with the infrastructure and capacity to execute.