Building a Doctor Appointment Assistant with OpenAI Function Calling (Python)
In this post, I’ll walk through a fully functional medical appointment assistant built using Python and OpenAI’s Responses API. This…
Building a Doctor Appointment Assistant with OpenAI Function Calling (Python)
In this post, I’ll walk through a fully functional medical appointment assistant built using Python and OpenAI’s Responses API. This implementation goes beyond a simple chatbot — it demonstrates structured tool calling, backend simulation, and controlled conversational flow.
If you’re exploring LLM-powered assistants with real-world workflows, this is a great reference architecture.
What This System Does
The assistant helps users:
- Browse doctor specializations
- View available doctors
- Check appointment slots
- Book and cancel appointments
All interactions are strictly structured in JSON, making the system predictable and production friendly.

Key Components of the Code
1. Mocked Backend Layer
The system simulates a backend using Python functions:
list_specializations()→ Returns available specializationslist_doctors()→ Returns doctor directoryget_current_date()→ Provides system date contextget_slots_by_doctor()→ Fetches available slotsbook_slot()/cancel_slot()→ Manages appointments
This abstraction allows the LLM to behave like it’s interacting with a real API.
2. Tool Definitions (Function Calling)
Each backend capability is exposed as a tool. These tools are passed to the model so that it can decide when a function needs to be called, extract the required structured arguments from the user’s input, and trigger the corresponding backend logic to complete the requested action.
tools = [
tools_book_doctor_appointment,
tools_list_doctors,
tools_list_specializations,
tools_get_current_date,
tools_get_available_slots
]
3. System Prompt (The Brain)
The system prompt is carefully designed to enforce strict JSON responses and ensure deterministic workflows throughout the interaction. It also requires the assistant to explicitly ask for clarification whenever there is any ambiguity, and prevents it from making assumptions, especially when it comes to handling dates.
The references of the key components are already present in my previous post, towards the end of the post. The same samples used in playground environment were used in this python implementation also.
Core Execution Flow
The conversation loop is handled by three key functions:
1. call_responses_api
Handles communication with the OpenAI API
@retry(wait=wait_random_exponential(multiplier=1, max=40), stop=stop_after_attempt(3))
def call_responses_api(messages, tools=None, tool_choice=None, model=GPT_MODEL):
try:
response = client.responses.create(
model=model,
input=messages,
tools=tools,
tool_choice=tool_choice,
)
return response
except Exception as e:
print("Unable to generate response from responses API")
print(f"Exception: {e}")
return e
2. handle_chat_response
Processes the model output by first detecting whether any function calls have been made, then executing the corresponding backend functions based on those calls, and finally appending the results back into the conversation so that the model can continue the interaction with the updated context.
def handle_chat_response(chat_response):
"""
Process chat_response output items and append results to messages.
"""
output_messages = []
for item in chat_response.output:
if item.type == "function_call":
output_messages.append({
"type": item.type,
"name": item.name,
"call_id": item.call_id,
"arguments": item.arguments
})
if item.name == "get_current_date":
output_data = get_current_date()
elif item.name == "list_specializations":
output_data = list_specializations()
elif item.name == "list_doctors":
output_data = list_doctors()
elif item.name == "book_doctor_appointment":
args = json.loads(item.arguments)
appointment_id = args['appt_id']
patient_name = args['patient_name']
output_data = book_slot(appointment_id, patient_name)
elif item.name == "get_available_slots":
args = json.loads(item.arguments)
doctorNum = args['doctorRegNo']
date = args['date']
output_data = get_slots_by_doctor(doctorNum, date)
else:
output_data = {"error": f"Unknown function {item.name}"}
# Append as assistant message with function result
output_messages.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(output_data, indent=2)
})
elif item.type == "message":
output_messages.append({
"role": "assistant",
"content": [
{
"type": "output_text",
"text": item.content[0].text
}
]
})
pretty_print_conversation(output_messages)
# Handle other types if needed
user_input = input("user_input; type quit to end the conversation")
user_messages = []
user_messages.append({
"role": "user",
"content": [
{
"type": "input_text",
"text": user_input
}
]
})
pretty_print_conversation(user_messages)
output_messages.extend(user_messages)
return output_messages # return the last appended message
3. run_conversation
Controls the iterative loop
def run_conversation(messages, tools, max_iterations=10):
"""
Iteratively call call_responses_api and handle_chat_response
until no new messages are added or max_iterations is reached.
"""
pretty_print_conversation(messages)
for _ in range(max_iterations):
prev_len = len(messages)
# Call the model
chat_response = call_responses_api(messages, tools=tools)
# Handle the response (append tool outputs, assistant replies, etc.)
output_messages = handle_chat_response(chat_response)
messages.extend(output_messages)
if len(messages) == prev_len:
# No new messages added → stop
break
return messages
Entry Point
messages = []
messages.append({
"role": "system",
"content": [
{
"type": "input_text",
"text": system_message
}
]
})
messages.append({
"role": "user",
"content": [
{
"type": "input_text",
"text": "Hi"
}
]
})
final_messages = run_conversation(messages, tools, 20)
Important Insight: The Cost of Stateless Conversations
Here’s the key takeaway from this implementation: every API call sends the entire conversation history to the model. In practice, this means that all previous messages, along with every tool call and each corresponding tool response, are repeatedly passed back to the LLM on every interaction.
This approach introduces a few important challenges. The token cost increases rapidly as the conversation grows, since more context needs to be transmitted each time. At the same time, latency also increases because the model has to process a larger input on every request. Over time, this makes the system harder to scale efficiently, especially for longer or more complex conversations.
Why It Happens
The OpenAI API in this setup is stateless, which means it does not retain any memory of previous interactions. To maintain context across the conversation, the entire message history must be explicitly resent with each request. This is done by extending the existing messages with new outputs using messages.extend(output_messages) and then passing the updated messages again in the next API call through call_responses_api(messages, tools=tools).
As a result, the model only knows what you include in the current request and has no inherent memory unless the full conversation context is provided every single time.
Final Thoughts
This implementation is a great example of how LLMs can be transformed into structured assistants, how function calling can be used effectively, and how predictable AI workflows can be designed. At the same time, it also highlights a real-world challenge that arises when building such systems.
Plain chatbot architectures become expensive and inefficient as conversations grow.
What’s Next?
To explore ways to maintain a memory of past interactions so that only the latest user message needs to be sent to the LLM, rather than repeatedly passing the entire conversation as tokens.
메타데이터
- post_id
- dcc51fcc7230
- slug
- building-a-doctor-appointment-assistant-with-openai-function-calling-python-dcc51fcc7230
- url
- https://medium.com/@saipriya.evolving/building-a-doctor-appointment-assistant-with-openai-function-calling-python-dcc51fcc7230
- canonical_url
- https://medium.com/@saipriya.evolving/building-a-doctor-appointment-assistant-with-openai-function-calling-python-dcc51fcc7230
- author_url
- https://medium.com/@saipriya.evolving
- status
- ok
- fetched_at
- 2026-06-23 17:05:31