基于LangGraph与OpenAI模型,端到端实现智能体AI客服及预约聊天机器人
引言
医疗行政工作往往繁琐且耗时。从预约挂号到管理医生出诊时间,诊所面临诸多组织管理层面的挑战。设想一套能通过对话式AI界面自动化完成全流程预约挂号的系统。
本文将讲解如何搭建一款智能诊所预约聊天机器人,使用LangGraph实现状态驱动的智能体调度,OpenAI的gpt 4o-mini完成自然语言理解,SQLite实现数据持久化,以及Streamlit打造直观的用户界面。
在这份完整指南中,我们将逐步拆解诊所预约系统的整体架构,详解每个组件的作用,以及各组件如何协同工作,实现流畅的预约体验。
整体解决方案设计
问题背景
诊所通常依赖人工流程或零散的系统管理患者预约,这会导致等待时间过长、排班冲突、医护人员工作效率低下、患者体验差等问题。患者可能难以查询可接诊医生、选择对应科室,或是便捷完成预约。诊所需要一套自动化智能系统,能够通过自然对话处理预约挂号,同时精准记录医生、患者及预约信息。
解决方案
本文提出的方案是一款AI驱动的诊所预约聊天机器人,结合前沿AI技术与网页技术,实现预约流程自动化。
系统使用LangGraph管理多步骤对话工作流,OpenAI的gpt-4o-mini理解用户自然语言请求,SQLite存储医生、患者及预约数据,Streamlit提供交互式聊天界面。
通过引导式对话,聊天机器人可实现:
-
问候用户并询问是否需要预约挂号
-
展示可预约的医学科室
-
显示对应医生及可预约时段
-
收集患者信息(姓名与电话)
-
确认预约并将预约信息存入数据库
该方案减轻行政工作负担、提升预约效率、避免排班冲突,同时通过AI对话界面为患者提供流畅的使用体验。
接下来我们开始实操。
先预览我们即将搭建的成品效果:
完整的端到端代码可参考我的GitHub仓库。我会在该仓库持续更新更多智能体AI应用案例,欢迎Star支持。
环境配置
- 创建虚拟环境
Mac / Linux / Windows
python -m venv .venv
2. 激活虚拟环境
Mac / Linux
source .venv/bin/activate
Windows(PowerShell)
.venv\Scripts\Activate.ps1
Windows(命令提示符)
.venv\Scripts\activate
激活后,终端会显示类似内容:
(.venv) your-folder-name %
3. 安装依赖包
以下是requirements.txt文件内容:
streamlit>=1.50.0
python-dotenv1.0.0
pydantic2.12.5
pandas2.3.3
python-dateutil2.8.2
langgraph>=1.0.7
openai>=2.16.0
pygraphviz==1.14
在终端执行:
cd clinic-agent
pip install -r requirements.txt
- 配置环境变量
在clinic-agent目录下创建.env文件:
OPENAI_API_KEY=your_openai_api_key_here
第一部分:数据库搭建(data/db.py)
任何预约系统的基础都是稳定的数据库。本诊所系统采用SQLite,包含三张核心表:医生表、患者表、预约表。
三张表相互关联,分别存储医生信息、患者信息及已预约记录。
数据库设计保证数据结构清晰、关联完整,便于预约流程中快速查询调用。
1. 医生表
医生表用于存储诊所内出诊医生的相关信息。
每条记录对应一位医生,包含其专业科室与出诊时间。聊天机器人可根据用户选择的科室,匹配对应出诊医生。
关键字段:
doctor_id:医生唯一标识
doctor_name:医生姓名
speciality:医学专业科室
office_timing:医生出诊时间
本案例设置5个科室:
-
皮肤科医生
-
骨科医生
-
全科医生
-
儿科医生
-
耳鼻喉科医生
下表为预设的医生姓名与出诊时间,你可自行扩展更多科室。
医生表
2. 患者表
患者表用于存储预约所需的患者信息。
用户每次预约时,系统会通过手机号校验该患者是否已存在;若不存在,则新建一条患者记录。
关键字段:
customer_id:患者唯一标识
name:患者姓名
phone:患者联系电话
3. 预约表
预约表是存储预约信息的核心数据表,每条预约记录关联一位医生与一位患者,并记录具体预约日期与时间。
关键字段:
booking_id:预约单唯一标识
doctor_id:关联接诊医生
customer_id:关联预约患者
appointment_date:预约日期
appointment_time:预约时间
status:预约状态(已确认、已取消等)
4. 创建数据库
我们先按上述设计定义数据库表结构,创建clinic.db数据库。
# data/db.py - 数据库初始化与操作
import sqlite3
import os
from datetime import datetime, timedelta
DB_PATH = os.path.join(os.path.dirname(__file__), "clinic.db")
def get_connection():
"""获取数据库连接"""
return sqlite3.connect(DB_PATH)
def init_db():
"""初始化数据库,创建表并插入示例数据"""
conn = get_connection()
cursor = conn.cursor()
# 医生表
cursor.execute("""
CREATE TABLE IF NOT EXISTS doctors (
doctor_id TEXT PRIMARY KEY,
doctor_name TEXT NOT NULL,
speciality TEXT NOT NULL,
office_timing TEXT NOT NULL
)
""")
# 患者表
cursor.execute("""
CREATE TABLE IF NOT EXISTS customers (
customer_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
phone TEXT NOT NULL
)
""")
# 预约表
cursor.execute("""
CREATE TABLE IF NOT EXISTS bookings (
booking_id TEXT PRIMARY KEY,
doctor_id TEXT NOT NULL,
customer_id TEXT NOT NULL,
appointment_date TEXT NOT NULL,
appointment_time TEXT NOT NULL,
status TEXT NOT NULL,
FOREIGN KEY (doctor_id) REFERENCES doctors (doctor_id),
FOREIGN KEY (customer_id) REFERENCES customers (customer_id)
)
""")
# 插入示例医生数据
doctors = [
("D1", "阿尼尔·夏尔马医生", "全科医生", "10:00-14:00"),
("D2", "内哈·维尔马医生", "皮肤科医生", "11:00-16:00"),
("D3", "罗希特·梅塔医生", "骨科医生", "09:00-13:00"),
("D4", "卡维塔·拉奥医生", "儿科医生", "10:00-15:00"),
("D5", "桑杰·耶尔医生", "耳鼻喉科医生", "12:00-17:00"),
]
for doctor in doctors:
cursor.execute(
"INSERT OR IGNORE INTO doctors (doctor_id, doctor_name, speciality, office_timing) VALUES (?, ?, ?, ?)",
doctor
)
conn.commit()
conn.close()
if __name__ == "__main__":
print("正在初始化数据库...")
init_db()
print("数据库初始化完成。")
在终端执行以下命令,运行数据库初始化脚本db.py:
cd clinic-agent
python data/db.py
执行后将生成包含三张表的clinic.db数据库文件。
第二部分:服务层(智能体调用工具)
服务层封装业务逻辑,实现智能体与数据库的交互。
LangGraph智能体不直接查询数据库,而是调用服务层函数完成查询医生信息、生成可预约时段、确认预约等操作。
在智能体架构中,这类服务可视为智能体执行操作的工具,智能体本身则专注于管理对话流程。
医生服务(services/doctor_service.py)
医生服务封装与医生相关的操作,提供科室、医生、时段管理的业务逻辑,主要功能:
-
从数据库查询可预约科室列表
-
根据所选科室获取医生详情
-
根据医生出诊时间生成可预约时段
-
将展示时段(如下午2:00) 转换为24小时制用于数据库存储
# services/doctor_service.py - 医生相关操作
from data.db import get_all_doctors, get_doctor_by_speciality, get_doctor_by_id
def get_specialities_list():
"""获取所有科室列表"""
doctors = get_all_doctors()
# 返回不重复的科室
return list(dict.fromkeys([doc[2] for doc in doctors]))
def get_doctor_info(speciality):
"""根据科室获取医生信息"""
doctor = get_doctor_by_speciality(speciality)
if doctor:
return {
"doctor_id": doctor[0],
"doctor_name": doctor[1],
"speciality": doctor[2],
"office_timing": doctor[3]
}
return None
def generate_time_slots(office_timing):
"""根据出诊时间生成按小时划分的预约时段
参数:
office_timing: 格式为"11:00-16:00"的字符串
返回:
预约时段列表,如["11:00 AM", "12:00 PM", ...]
"""
start_time, end_time = office_timing.split("-")
start_hour = int(start_time.split(":")[0])
end_hour = int(end_time.split(":")[0])
slots = []
for hour in range(start_hour, end_hour):
if hour < 12:
suffix = "上午"
display_hour = hour if hour > 0 else 12
elif hour == 12:
suffix = "下午"
display_hour = 12
else:
suffix = "下午"
display_hour = hour - 12
slots.append(f"{display_hour}:00 {suffix}")
return slots
def parse_time_slot(slot_str):
"""将预约时段字符串转换为24小时制
参数:
slot_str: 格式为"1:00 PM"的字符串
返回:
格式为"13:00"的字符串
"""
time_part, suffix = slot_str.split(" ")
hour, minute = time_part.split(":")
hour = int(hour)
if suffix == "PM" and hour != 12:
hour += 12
elif suffix "AM" and hour 12:
hour = 0
return f"{hour:02d}:{minute}"
预约服务(services/booking_service.py)
预约服务处理患者管理与预约相关操作,主要功能:
-
通过手机号新建患者记录或查询已有患者
-
查询医生某一日期已被预约的时段
-
筛选并返回可预约时段
-
生成唯一预约单号,并将预约信息存入数据库
-
确认并保存最终预约详情(医生、患者、日期、时间)
# services/booking_service.py - 预约相关操作
import uuid
from datetime import datetime
from data.db import (
create_customer,
create_booking,
get_customer_by_phone,
get_bookings_by_doctor_and_date,
get_booking_by_id
)
from services.doctor_service import parse_time_slot
def get_or_create_customer(name, phone):
"""查询已有患者或新建患者"""
customer = get_customer_by_phone(phone)
if customer:
return customer[0] # 返回患者ID
customer_id = f"CUST-{uuid.uuid4().hex[:6].upper()}"
create_customer(customer_id, name, phone)
return customer_id
def get_available_slots(doctor_id, office_timing):
"""获取医生当日可预约时段
参数:
doctor_id: 医生ID
office_timing: 格式为"11:00-16:00"的出诊时间字符串
返回:
可预约时段列表
"""
from services.doctor_service import generate_time_slots
today = datetime.now().strftime("%Y-%m-%d")
all_slots = generate_time_slots(office_timing)
# 查询已预约时段
booked_times = get_bookings_by_doctor_and_date(doctor_id, today)
# 过滤已预约时段
available = []
for slot in all_slots:
slot_24h = parse_time_slot(slot)
if slot_24h not in booked_times:
available.append(slot)
return available
def confirm_booking(doctor_id, customer_name, customer_phone, time_slot, appointment_date=None):
"""确认预约
参数:
doctor_id: 医生ID
customer_name: 患者姓名
customer_phone: 患者电话
time_slot: 预约时段,如"1:00 PM"
appointment_date: 可选,格式为YYYY-MM-DD的预约日期,默认为当日
返回:
预约单号
"""
# 查询或创建患者
customer_id = get_or_create_customer(customer_name, customer_phone)
# 生成预约单号
booking_id = f"BKG-{uuid.uuid4().hex[:6].upper()}"
# 格式化预约时间
if not appointment_date:
appointment_date = datetime.now().strftime("%Y-%m-%d")
appointment_time = parse_time_slot(time_slot)
# 创建预约记录
create_booking(booking_id, doctor_id, customer_id, appointment_date, appointment_time)
return booking_id
服务层测试
# test/test_service.py - 测试已创建的服务
from pathlib import Path
import sys
# 允许直接运行该文件:`python test/test_service.py`
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from services.doctor_service import get_specialities_list, generate_time_slots
from services.booking_service import confirm_booking
# 获取所有科室
specialities = get_specialities_list()
print("可预约科室:", specialities)
# 为医生生成预约时段(上午11:00 - 下午4:00)
slots = generate_time_slots("11:00-16:00")
print("可预约时段:", slots)
# 确认预约
booking_id = confirm_booking(
doctor_id="D1",
customer_name="张三",
customer_phone="13800138000",
time_slot="2:00 PM"
)
print(f"预约已确认:{booking_id}")
在终端执行:
cd clinic-agent
python test_service.py
终端会输出以下内容:
(.venv) (base) my-mac clinic-agent % python test_service.py
可预约科室: ['全科医生', '皮肤科医生', '骨科医生', '儿科医生', '耳鼻喉科医生']
可预约时段: ['11:00 上午', '12:00 下午', '1:00 下午', '2:00 下午', '3:00 下午']
预约已确认:BKG-C4F60A
完美!服务层运行正常!
接下来进入最核心的部分:智能体层。
第三部分:智能体层
聊天机器人的核心是基于LangGraph的智能体,通过多个阶段管理完整的预约工作流。
预约状态
我们首先定义预约状态,这是存储对话全程所有信息的结构化记忆体。
核心作用:
-
跟踪当前对话阶段(如问候、选择医生等),引导工作流执行
-
存储用户选择的科室、医生、时段等信息
-
维护患者与预约详情,支撑智能体完成预约流程
# agents/booking_agent.py - LangGraph智能体实现
from typing import TypedDict, Annotated, List, Optional
from langgraph.graph import StateGraph, END
from openai import OpenAI
import os
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
class BookingState(TypedDict):
"""预约对话状态"""
messages: List[dict] # 聊天记录
stage: str # 问候、选择科室、选择医生等阶段
selected_speciality: Optional[str] # 所选医学科室
selected_doctor: Optional[dict] # 所选医生信息
selected_date: Optional[str] # 预约日期
selected_slot: Optional[str] # 预约时段
customer_name: Optional[str] # 患者姓名
customer_phone: Optional[str] # 患者电话
booking_id: Optional[str] # 预约确认单号
available_options: List[str] # 界面可选选项
def create_initial_state():
"""创建对话初始状态"""
return {
"messages": [],
"stage": "greeting",
"selected_speciality": None,
"selected_doctor": None,
"selected_date": None,
"selected_slot": None,
"customer_name": None,
"customer_phone": None,
"booking_id": None,
"available_options": []
}
大语言模型工具函数
接下来定义call_llm函数,作为与OpenAI模型交互的统一入口。
核心作用:
-
向大模型发送系统提示与用户提示
-
返回智能体所需的生成结果
-
保证智能体代码中大模型交互逻辑统一、可复用
# agents/booking_agent.py - LangGraph智能体实现
def call_llm(
system_prompt: str,
user_prompt: str,
*,
model: str = "gpt-4o-mini",
temperature: float = 0,
max_tokens: int = 50,
) -> str:
"""
统一封装大模型调用工具
返回助手回复内容
"""
try:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=temperature,
max_tokens=max_tokens,
)
return response
except Exception as e:
print(f"大模型调用异常:{e}")
return ""
智能体节点
代表预约工作流中处理特定任务的独立步骤。
核心作用:
-
处理问候、选择科室、确认预约等不同阶段
-
处理用户输入并更新对话状态
-
保证聊天机器人工作流模块化、易维护
智能体中定义多个节点,每个节点对应一个处理阶段:
-
greeting_node:问候用户并询问是否需要预约
-
select_speciality_node:展示可预约科室
-
select_doctor_node:展示所选科室对应的医生
-
select_date_node:供用户选择预约日期
-
select_slot_node:展示可预约时段
-
confirm_node:请求用户确认预约
-
collect_details_node:收集患者姓名与电话
-
completed_node:确认预约完成
-
cancelled_node:处理预约取消操作
构建流程图
接下来通过连接智能体节点,搭建状态驱动的对话工作流。
build_booking_graph函数核心作用:
-
添加代表不同预约阶段的节点
-
定义节点间的条件跳转逻辑
-
构建聊天机器人完整对话路径
# agents/booking_agent.py - LangGraph智能体实现
from langgraph.checkpoint.memory import MemorySaver
def build_booking_graph():
"""构建LangGraph工作流"""
workflow = StateGraph(BookingState)
# 添加所有节点
workflow.add_node("greeting", greeting_node)
workflow.add_node("select_speciality", select_speciality_node)
workflow.add_node("select_doctor", select_doctor_node)
workflow.add_node("select_date", select_date_node)
workflow.add_node("select_slot", select_slot_node)
workflow.add_node("confirm", confirm_node)
workflow.add_node("collect_details", collect_details_node)
workflow.add_node("completed", completed_node)
workflow.add_node("cancelled", cancelled_node)
# 设置入口节点
workflow.set_entry_point("greeting")
# 添加基于路由的条件边
workflow.add_conditional_edges(
"greeting",
llm_router,
{
"greeting": "greeting",
"select_speciality": "select_speciality",
"cancelled": "cancelled"
}
)
# 其他节点的条件边配置同理...
# 最终指向结束节点
workflow.add_edge("completed", END)
workflow.add_edge("cancelled", END)
# 编译并添加内存检查点用于会话管理
return workflow.compile(checkpointer=MemorySaver())
# 创建编译后的流程图
booking_graph = build_booking_graph()
接下来通过save_langgraph_flow.py可视化流程图:
#agents/save_langgraph_flow.py
"""将诊所预约LangGraph流程保存为PNG格式至当前文件夹"""
from pathlib import Path
import sys
# 保证脚本可在项目根目录或当前文件夹运行
AGENTS_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = AGENTS_DIR.parent
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from agents.booking_agent import booking_graph # noqa: E402
def save_graph_files() -> None:
"""导出流程图为PNG"""
graph = booking_graph.get_graph()
png_path = AGENTS_DIR / "langgraph_flow.png"
png_data = graph.draw_mermaid_png()
png_path.write_bytes(png_data)
print(f"流程图PNG已保存至:{png_path}")
if __name__ == "__main__":
save_graph_files()
在终端执行以下命令:
cd clinic-agent
python save_langgraph_flow.py
执行后会在agents文件夹下生成langgraph_flow.png:
LangGraph流程图
消息处理
该函数接收用户输入,并通过LangGraph工作流处理。
核心操作:
-
接收来自用户界面的消息
-
将消息传入智能体流程图处理
-
返回更新后的状态与助手回复
# agents/booking_agent.py - LangGraph智能体实现
def process_message(state: BookingState, user_message: str, thread_id: str = "default_session") -> BookingState:
"""通过预约流程图处理用户消息"""
config = {"configurable": {"thread_id": thread_id}}
# 检查流程图是否处于中断状态
current_state = booking_graph.get_state(config)
if current_state.tasks and current_state.tasks[0].interrupts:
# 使用用户输入恢复流程图执行
result = booking_graph.invoke(Command(resume=user_message), config=config)
else:
# 无中断,正常启动/继续流程
# 若为初始问候则不重复添加用户消息
if user_message.lower() != "hi" or state["messages"]:
# 避免重复添加用户消息
if not state["messages"] or state["messages"][-1].get("content") != user_message:
state["messages"].append({
"role": "user",
"content": user_message
})
# 运行流程图
result = booking_graph.invoke(state, config=config)
# 更新可选选项并确保消息存入聊天记录
snapshot = booking_graph.get_state(config)
if snapshot.tasks and snapshot.tasks[0].interrupts:
interrupt_value = snapshot.tasks[0].interrupts[0].value
# 同时兼容字典与字符串类型的中断值
msg_content = ""
options = []
if isinstance(interrupt_value, dict):
msg_content = interrupt_value.get("content", "")
options = interrupt_value.get("available_options", [])
else:
msg_content = str(interrupt_value)
# 确保中断消息存入聊天记录
if msg_content:
# 检查节点是否已添加该消息
last_msg_content = result["messages"][-1].get("content", "") if result["messages"] else ""
if last_msg_content != msg_content:
result["messages"].append({
"role": "assistant",
"content": msg_content,
"options": options
})
else:
# 若已添加则补充缺失的选项
result["messages"][-1]["options"] = options
result["available_options"] = options
else:
# 无中断时使用状态内设置,默认空列表
if "available_options" not in result:
result["available_options"] = []
return result
智能体测试
接下来测试智能体功能:
# test/test_agent.py - 智能体测试
# 初始化状态
from pathlib import Path
import sys
# 允许直接运行该文件:`python test/test_agent.py`
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from agents.booking_agent import create_initial_state, process_message
state = create_initial_state()
# 处理消息
state = process_message(state, "Hi", thread_id="session_1")
print(state["messages"][-1]["content"])
state = process_message(state, "I want to book", thread_id="session_1")
print(state["available_options"])
在终端执行:
cd clinic-agent
python test/test_agent.py
终端会正常展示可选择项。
接下来我们基于Streamlit搭建用户界面。
第四部分:Streamlit界面
Streamlit界面为用户提供美观、交互式的聊天体验。
功能特点:
-
实时聊天界面
-
可点击的选项按钮
-
唯一ID会话管理
# ui/chat_ui.py - 诊所预约聊天机器人Streamlit界面
"""诊所预约聊天机器人Streamlit界面"""
import streamlit as st
from agents.booking_agent import create_initial_state, process_message
from data.db import init_db
def initialize_session():
"""初始化会话状态"""
if "state" not in st.session_state:
st.session_state.state = create_initial_state()
if "initialized" not in st.session_state:
st.session_state.initialized = False
if "session_id" not in st.session_state:
import uuid
st.session_state.session_id = str(uuid.uuid4())
def display_chat_history():
"""展示聊天记录,保留选项与样式"""
messages = st.session_state.state.get("messages", [])
for i, message in enumerate(messages):
if message["role"] == "assistant":
with st.chat_message("assistant"):
st.markdown(message["content"])
# 存在选项则展示
options = message.get("options", [])
if options:
# 若为最新消息且未完成预约,展示可点击按钮
if i == len(messages) - 1 and st.session_state.state["stage"] not in ["completed", "cancelled"]:
st.markdown("---")
# 创建按钮列
cols = st.columns(min(len(options), 3))
for idx, option in enumerate(options):
col_idx = idx % 3
with cols[col_idx]:
if st.button(option, key=f"btn_{i}_{idx}", use_container_width=True):
handle_user_input(option)
else:
# 历史消息以标签形式展示选项
options_str = " ".join([f"`{opt}`" for opt in options])
st.markdown(f"**可选选项:** {options_str}")
else:
with st.chat_message("user"):
st.markdown(message["content"])
def handle_user_input(user_input: str):
"""处理用户输入并通过智能体执行"""
# 处理消息
st.session_state.state = process_message(
st.session_state.state,
user_input,
thread_id=st.session_state.session_id
)
# 刷新界面更新
st.rerun()
def run_chat_ui():
"""运行聊天界面"""
# 页面配置
st.set_page_config(
page_title="康护佳诊所——预约挂号",
page_icon="🏥",
layout="centered"
)
# 自定义CSS区分聊天消息
st.markdown("""
<style>
[data-testid="stChatMessageUser"] {
flex-direction: row-reverse;
text-align: right;
background-color: #e0f2f1;
border-radius: 15px 15px 0px 15px;
}
[data-testid="stChatMessageAssistant"] {
background-color: #f5f5f5;
border-radius: 15px 15px 15px 0px;
}
</style>
""", unsafe_allow_html=True)
# 初始化数据库
init_db()
# 初始化会话
initialize_session()
# 标题
st.title("🏥 康护佳诊所")
st.markdown("*轻松预约医生号源*")
st.markdown("---")
# 未初始化时发送初始问候
if not st.session_state.initialized:
st.session_state.state = process_message(
st.session_state.state,
"Hi",
thread_id=st.session_state.session_id
)
st.session_state.initialized = True
st.rerun()
# 展示聊天记录
display_chat_history()
# 预约未完成时显示输入框
if st.session_state.state["stage"] not in ["completed", "cancelled"]:
if prompt := st.chat_input("在此输入消息..."):
handle_user_input(prompt)
else:
# 完成后显示重新预约按钮
st.markdown("---")
if st.button("🔄 重新预约", use_container_width=True):
st.session_state.state = create_initial_state()
st.session_state.initialized = False
st.rerun()
第五部分:应用入口文件
接下来定义app.py,作为启动整个聊天机器人应用的入口。
# app.py - 应用主入口
from ui.chat_ui import run_chat_ui
if __name__ == "__main__":
run_chat_ui()
运行聊天机器人:两种方式
方式一:Streamlit网页应用
最简单、用户体验最优的运行方式。
执行以下命令启动应用:
streamlit run app.py
聊天机器人将在http://localhost:8501打开。
预约聊天机器人Streamlit应用界面
方式二:Jupyter Notebook
适用于开发、测试与调试聊天机器人逻辑。
在Jupyter Notebook中导入所需函数:
# clinic-agent.ipynb
from services.doctor_service import get_specialities_list, get_doctor_info, generate_time_slots
from services.booking_service import confirm_booking
from agents.booking_agent import (
BookingState,
create_initial_state,
build_booking_graph,
process_message
)
随后构建流程图并在Notebook中可视化:
# clinic-agent.ipynb
# 初始化预约流程图
booking_graph = build_booking_graph()
## 可视化预约流程图结构
from IPython.display import Image, display
png_bytes = booking_graph.get_graph().draw_mermaid_png()
display(Image(png_bytes))
运行机器人会话:
在Notebook内运行交互式聊天机器人会话:
# clinic-agent.ipynb
from langgraph.types import Command
def run_booking_session(graph, thread_id="notebook_session", reset=False):
config = {"configurable": {"thread_id": thread_id}}
# 1. 启动或重置逻辑
current_state = graph.get_state(config)
if reset or not current_state.values:
print(f"--- {'🔄 正在重置' if reset else '🆕 正在初始化'} 会话 ---")
# 调用invoke直接启动问候节点
graph.invoke(create_initial_state(), config=config)
print("---⚕⚕ 康护佳预约会话开始 ---")
last_displayed_message_idx = -1 # 记录已展示的消息索引
while True:
state = graph.get_state(config)
# 展示未显示的新助手消息(处理无关话题回复)
if state.values and state.values.get('messages'):
messages = state.values['messages']
for idx in range(last_displayed_message_idx + 1, len(messages)):
msg = messages[idx]
if msg.get("role") == "assistant":
print(f"\n[AI]:{msg['content']}")
last_displayed_message_idx = len(messages) - 1
# 2. 检查中断状态
if state.tasks and state.tasks[0].interrupts:
interrupt_info = state.tasks[0].interrupts[0].value
# --- 修复:兼容字符串与字典类型中断 ---
if isinstance(interrupt_info, dict):
ai_message = interrupt_info.get('content', '无消息内容')
options = interrupt_info.get('available_options', [])
else:
ai_message = interrupt_info
options = []
print(f"\n[AI]:{ai_message}")
if options:
print(f"可选选项:{', '.join(options)}")
# ---------------------------------------
user_input = input("\n[你]:")
print(f"[你]:{user_input}")
# 使用用户输入恢复流程
graph.invoke(Command(resume=user_input), config=config)
# 3. 检查流程是否结束
elif not state.next:
# 结束前打印最终助手消息
if state.values and state.values.get('messages') and state.values['messages'][-1]["role"] == "assistant":
if last_displayed_message_idx < len(state.values['messages']) - 1:
print(f"\n[AI]:{state.values['messages'][-1]['content']}")
print("\n--- ⚑⚑ 会话结束 ---")
break
# 4. 存在待执行节点且无中断,继续执行
else:
graph.invoke(None, config=config)
# 重要:仅需重置历史时设置reset=True
# 设为False可继续原有对话
run_booking_session(booking_graph, reset=True)
Notebook中的诊所智能体聊天机器人
注意:Notebook的输入框会显示在顶部,如上图所示。
仓库内已提供clinic-agent.ipynb文件,可直接运行完整流程。
总结
本文基于LangGraph、OpenAI GPT、SQLite与Streamlit,搭建了一款AI驱动的诊所预约聊天机器人。通过结合状态驱动的智能体工作流与对话界面,系统可引导用户完成从选择科室到确认预约的全流程。分层架构将数据库、服务层、智能体逻辑与界面解耦,使应用模块化、易维护、易扩展。
该方案展示了对话式AI如何简化预约挂号这类实际工作流程,同时减少人工行政工作量。系统可进一步扩展日历集成、消息通知、多语言支持、社交平台对接等功能。整体而言,本项目是现代AI框架与轻量化网页工具结合,打造智能、易用医疗应用的实用案例。
-------------------------------------------------------------