How I Built a Healthcare Chatbot Without Prompt Engineering—Thanks to Parlant’s Guideline-Driven AI

by Yann BohbotMay 22nd, 2025
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

A conversational healthcare agent built with Parlant, designed to assist patients with appointment rescheduling, doctor availability, and lab work preparation.

People Mentioned

Mention Thumbnail
featured image - How I Built a Healthcare Chatbot Without Prompt Engineering—Thanks to Parlant’s Guideline-Driven AI
Yann Bohbot HackerNoon profile picture
0-item

I want to share my experience building customer-facing AI conversations with Parlant. Building reliable AI conversations became surprisingly straightforward with it. Rather than endlessly fiddling with prompts trying to get them to behave properly, I can now precisely control how my agents respond to specific scenarios with clear, simple, contextual guidelines.

What I like most about Parlant is that it aligns with how I think about conversations, not as standalone responses (like most Q&A RAG agents), but as ongoing interactions that shift, adapt, and carry context forward.

The project I was working on and sharing here is designed around three healthcare-focused use cases, each demonstrated through natural interaction examples. These conversations will simulate real exchanges between a patient and a healthcare assistant, covering appointment booking, medication information, and insurance policy management.

The goal in this post is to share my journey using Parlant and walk through how I structured the project, how simple it is to set up an agent, define its guidelines, and connect it to my tools. Along the way, I'll highlight some roadblocks I encounter and explain how to overcome them. Beyond showing how easy it is to build agents with Parlant, I also want to dive into best practices on how to ensure accurate, reliable responses.

My Use Case

My first use case focuses on helping a patient reschedule an appointment with their doctor. I'll show how the agent sticks to the rules and guidelines I've set, handling context shifts smoothly while keeping responses clear and accurate. Here's the ideal conversation I'm looking to achieve:

The Ideal Conversation:

Patient: Hi, I need to reschedule my appointment with Dr. Chen please.

Agent: Hey Sarah! I'd be happy to help reschedule your appointment with Dr. Chen. I see your appointment is scheduled for March 18th, at 2:30 PM. What day or time would work better for you?

Patient: I need something preferably in the morning.

Agent: Let me check Dr. Chen's availability for those times. I see openings on Wednesday the 26th at 9:30 AM or Friday the 28th at 10:15 AM. Would either of those work for you?

Patient: The Friday appointment works. Also, will I need to do any lab work before this appointment?

Agent: I've rescheduled your appointment for Friday, March 28th at 10:15 AM with Dr. Chen. Regarding lab work, I see Dr. Chen has ordered a basic metabolic panel for you. You should come in 15 minutes early to have blood drawn before your appointment. Is there anything else you need help with today?

Patient: That's all. Thank you!

Agent: You're welcome, Sarah. You'll receive a confirmation via email shortly. Have a great day!

Structuring My Application - Laying the Groundwork

I don't want you to get lost with my setup. The order in which you build your agent is totally up to you. I'll do my best to explain every step I took in structuring my application.

Setup with Poetry

I'm creating this project with poetry, but pip install parlant works the same.


poetry new healthcare-agent
cd healthcare-agent
poetry add parlant 

Databases

I'm using two separate JSON files: one for patients and one for doctors. The patients.json includes all relevant information, such as contact details, upcoming appointments, lab results, medications, and allergies, for every patient.

The doctors.json contains the doctors' specialty, availability, current patients, and scheduled appointments for each as well. The goal is to keep both files in sync, so that when a new patient tries to book an appointment with a doctor, they'll see the most up-to-date availability.

My Agent Tools

For me, planning and preparation are key to building a good application, possibly a large-scale one. I start by noting what tools my agent needs to assist the patient in rescheduling an appointment. When users say, “I want to schedule an appointment with my doctor,” I expect my agent to automatically recognize the patient, identify their doctor, and assist with their booking request.

Here's how I'm breaking this down:

Identifying The Patient: A tool to fetch the patient's data from the patients.json. It's triggered as a tool-enabled context variable, ensuring that the agent always has access to the most up-to-date patient information throughout the conversation.

Listing Doctors' Availability: A tool to fetch the doctors' availability slots. It is triggered when the patient asks for their doctor's availability in any form within the conversation.

Rescheduling an Appointment: A tool to reschedule an existing appointment. It is triggered when the patient confirms the scheduled time slot they want to reschedule and the new time slot they wish to reschedule the appointment with.

My focus here is rescheduling, but I made sure the agent can also handle new appointments and cancellations. I'm not covering them here, but they're part of the broader flow.

Building My Healthcare Agent

I'm going to start setting up my healthcare agent. First, I'll run the server on my terminal.


parlant-server run

Creating My Agent

On a new terminal, I'll create my agent and give it a name and a personality through this command:


parlant agent create `
--name "David" `
--description "You are a detail-oriented healthcare agent who combines
warm empathy with remarkable efficiency, maintaining a gentle sense of 
humor to put anxious patients at ease, while calmly managing complex
medical needs without forgetting previously discussed details, and
skillfully prioritizing urgent matters without dropping routine tasks."


Creating My Guidelines

This part is really fun. I'll create a guideline where a patient requests to schedule an appointment, and the agent asks for the patient's preferred times. I'll also create another guideline to have the agent suggest time slots based on the patient's preferences. I'm just testing this out to make sure my agent focuses on the instructions I give.


parlant guideline create `
--condition "patient asks to schedule an appointment" `
--action "ask the patient for their preferred time"


parlant guideline create `
--condition "patient gives preferred times for scheduling an appointment" `
--action "provide timeslots from 9:00AM to 11:00AM for morning preferences and 1:00PM to 5:00PM for afternoon preferences"

Testing It Out - Sandbox UI

To test it out, I'll navigate to Parlant's Sandbox UI at http://localhost:8800/chat and start a conversation with David, my healthcare assistant.

Parlant's Sandbox UI gives me a lot of insights into how my agent reacts to the conversation. It allows me to debug its behavior and understand its thinking process, something that you don't see much in other frameworks.

Building My Conversation Flow

This is where things get interesting. I'm going to build a flow that replicates the conversation experience from the example I listed above. I'll create service tools that work with my databases and tie them to my guidelines. When a guideline is triggered, my agent will use those tools I built to respond to the patient based on the conversation's context.

Setting Up My Service Tools

I'll create my service tools with the module command. This command generates a boilerplate module and automatically registers it in my ./parlant.toml, so the server loads it at startup.


parlant-server module create module --template tool-service

My module.py is now located at the core of my application. However, I've reordered my files to match each conversation topic, and I'll have a module.py built that acts as the main service entry point for each topic.


healthcare-agent/
└── modules/
    └── appointments/
        ├── module.py        # module entry point
        ├── general.py       # general functions
        ├── scheduling.py    # scheduling functions
        ├── rescheduling.py  # rescheduling functions
        └── cancelling.py    # cancelling functions

These are the tools I'm using through the module.py in my ./modules/appointments


from contextlib import AsyncExitStack
from lagom import Container
from typing import Annotated

from parlant.sdk import (
    PluginServer,
    ServiceRegistry,
    ToolContext,
    ToolParameterOptions,
    ToolResult,
    tool,
)

from modules.appointments.general import get_patient_data, get_doctors_availability
from modules.appointments.rescheduling import reschedule_appointment

EXIT_STACK = AsyncExitStack()

PORT = 8199
TOOLS = [
        get_patient_data,
        get_doctors_availability,
        reschedule_appointment,
]

async def initialize_module(container: Container) -> None:
    host = "127.0.0.1"

    server = PluginServer(
        tools=TOOLS,
        port=PORT,
        host=host,
        hosted=True,
    )

    await container[ServiceRegistry].update_tool_service(
        name="appointments",
        kind="sdk",
        url=f"http://{host}:{PORT}",
        transient=True,
    )

    await EXIT_STACK.enter_async_context(server)
    EXIT_STACK.push_async_callback(server.shutdown)


async def shutdown_module() -> None:
    await EXIT_STACK.aclose()


Building My Custom Tools

With Parlant, building tools requires me to use ToolContext as a tool parameter and ToolResult to return the output.


@tool
def mytool(context: ToolContext) -> ToolResult:
    # your code here
    return ToolResult(data)

The ToolContext gives me access to things like customer_id, agent_id and session_id that are used in the conversation. Then, the function returns the data wrapped in a ToolResult, which is stored in the conversation context.

My First Tool - Doctor's Availability

I'm going to store all my information-fetching tools inside my general.py. This will include getting patients' data records, doctors' availability, healthcare coverage, and more, if I need to add.

I'm going to start with fetching a doctor's availability. Here's an Idea of how this would look.


import json
from parlant.core.services.tools.plugins import tool
from parlant.core.tools import ToolContext, ToolResult

DOCTORSDB_PATH = "doctors_database_path"

@tool
def get_doctors_availability(context: ToolContext, doctor_name:str) -> ToolResult:
    doctors_data=fetch(Path(DOCTORSDB_PATH))
    doctor = next((entry["doctor"] for entry in doctors_data if entry["doctor"]["name"] == doctor_name), None)

    if doctor is None:
        return ToolResult(f"No doctor with the name {doctor_name}")

    availabilities = doctor["doctor"]["scheduling"]["availability"]
    sorted_availability = _sort_datetime(availabilities)
    sorted_availability = availabilities
    return ToolResult(sorted_availability)

I'll have this tool trigger when a patient asks about a doctor's availability schedule. The tool takes the doctor's name as an argument and searches through the doctors' database. I'll trigger it with this guideline:


parlant guideline create `
--condition "patient asks about a doctor's availability" `
--action "suggest the two most recent availability slots"

Now to tie it with my tool:


parlant guideline tool-enable `
--id $GUIDELINE_ID `
--service "appointments"
--tool "get_doctors_availability"

Let's test out the result:

Like a toolbox pre-loaded for the job at hand!

All right, so I covered a few key parts of my project. Still, I'm missing an important piece in my application. I want my agent to recognize the patient they're speaking to. For that, I'm going to implement Parlant's customer feature, which will allow the agent to identify the patient and retrieve their data record.

Identifying Patients

In Parlant, a “customer” is just anyone talking to the agent. They are set in the Parlant server, and I can personalize my agent's behavior for each one. For now, I just want my agent to recognize them and get their medical information. I'm going to create a customer with Parlant and have its ID match the one I have in my databases.


parlant customer create --name "Sarah Johnson"

This returns a customer ID that I'll match with my patient ID in my patients.json database and will be shared with the agent through the conversation's context. So when the agent calls get_patient_data or any tool needing a patient ID, it will access it from the tool's ToolContext using customer_id.


@tool
def get_patient_data(context: ToolContext) -> ToolResult:
    patients_db = fetch(PATIENTSDB_PATH)
    patient = next(entry["patient"] for entry in patients_db if entry["patient"]["patient_id"] == context.customer_id, None)

    return ToolResult(patient)

Now, when the tool is called, it will return the data record for the patient whose context.customer_id matches the patient_id from the patients database.

Keeping Track Of The Latest Record Updates

I mentioned earlier that there's a way to ensure that my agent works with the most up-to-date data from the patient in the database. This stems from a common misuse of guidelines, which I initially fell into as well.

At first, I thought I needed to trigger the get_patient_data tool using a guideline, where the condition is that it triggers at the start of the conversation. The problem with that approach is that the agent only fetches the data at the beginning. If that data changes later, the agent won't call the get_patient_data tool again because the trigger condition no longer applies.

The right way to do it is to apply the tool-enabled context variable. It's a way to continuously run the get_patient_data tool, on every agent response. Here's how to do it:


parlant variable create `     
--description "Getting the patient the most up-to-date medical record" `
--name "up-to-date-patient-medical-record" `
--service "appointments" `
--tool "get_patient_data"

I can also control the time frames in which I want this tool to be triggered by adding --freshness-rules

Time to put it to the test:

Cooking Up the Last Bits

Now that I've walked through the baseline of building my healthcare agent, it's time to pull it all together. I'm going to wrap this up and bring the rest of the implementation to life.

These are the guidelines I've set in place:


parlant guideline create `
--condition "patient requests to reschedule an appointment" `
--action "refer the patient's scheduled appointments and ask which appointment they want to reschedule"

parlant guideline create `
--condition "patient chooses the appointment they want to reschedule" `
--action "ask the patient what day or time would work better for them to reschedule."

parlant guideline create `
--condition "patient provides a preferred day or time for rescheduling" `
--action "provide the doctor's next two available appointment slots based on the user's preference"

parlant guideline create `
--condition "patient chooses appointment slot for rescheduling with their doctor" `
--action "assist and confirm the appointment in detail"

Now, I'll set the tools I want to trigger:

This guideline:


Condition: patient provides a preferred day or time for rescheduling

Action: provide the doctor's next two available appointment slots based on the user's preference

Triggers this tool: get_doctors_availability

This guideline:

Condition: patient chooses appointment slot for rescheduling with their doctor

Action: assist and confirm the appointment in detail

Triggers this tool: reschedule_appointment

First Tentative Conversation

This was the first full conversation I had with the agent, start to finish. My biggest learning curve wasn't in the setup (which felt smooth and straightforward), but simply learning how to control the agent through guidelines and tools.

What I really appreciate is that I don't need to rely on excessive prompt tuning to get the agent to follow my “business needs”. Parlant already has a smart system in place for that, called ARQs.

My agent now follows my instructions the way I wanted it to. Regardless of that, this isn't the conversation experience I'm expecting to have (Obi-Wan Kenobi mind control gesture). Thankfully, Parlant addresses this, too.

In my next post, I'll cover how to match the conversation style of my ideal example using Styled Utterances. I'll walk through how to build utterance templates that reflect the tone I want and show how to restrict the agent to only use responses generated from those templates when generating responses.

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks