为诊所构建客服智能体

医疗行政管理往往繁琐且耗时。从预约安排到管理医生出诊时间,诊所面临着众多组织挑战。想象一下,一个系统通过对话式AI界面自动化整个预约流程。

本文探讨如何使用LangGraph进行状态驱动的智能体编排、OpenAI的gpt 4o-mini进行自然语言理解、SQLite进行数据持久化、以及Streamlit提供直观的用户界面,来构建一个智能诊所预约聊天机器人

在本综合指南中,我们将介绍诊所预约系统的完整架构,了解每个组件、其目的,以及它们如何协同工作以创造无缝的预约体验。

高级解决方案设计

1、问题陈述

诊所通常依赖手动流程或碎片化的系统来管理患者预约。这导致漫长的等待时间、预约冲突、员工工作负载效率低下以及糟糕的患者体验。患者可能难以找到可用的医生、选择合适的专科或轻松预约。诊所需要一个自动化、智能的系统,能够通过自然对话处理预约安排,同时维护医生、患者和预约的准确记录。

2、解决方案

提出的解决方案是一个AI驱动的诊所预约聊天机器人,使用现代AI和网络技术自动化预约安排流程。

该系统使用LangGraph管理多步骤对话工作流,OpenAI的gpt-4o-mini理解自然语言的用户请求,SQLite存储医生、患者和预约数据,Streamlit提供交互式聊天界面。

通过引导式对话,聊天机器人:

  1. 问候用户并询问是否想要预约
  2. 显示可用的医疗专科
  3. 显示医生和可用时间段
  4. 收集患者详情(姓名和电话)
  5. 确认并将预约存储在数据库中

该解决方案减少管理工作量、提高预约效率、防止预约冲突,并通过AI对话界面提供无缝的患者体验。完整的端到端代码可以参考我的GitHub仓库

让我们开始吧。

3、环境设置

1. 创建虚拟环境:

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-dotenv==1.0.0
pydantic==2.12.5
pandas==2.3.3
python-dateutil==2.8.2
langgraph>=1.0.7
openai>=2.16.0
pygraphviz==1.14

在终端中,

cd clinic-agent
pip install -r requirements.txt

4. 设置环境变量:

clinic-agent目录中创建.env文件:

OPENAI_API_KEY=your_openai_api_key_here

4、数据库设置 (data/db.py)

任何预约系统的基础都是健壮的数据库。该诊所使用SQLite,包含三个主要表:doctors、customers和bookings。

这些表协同工作,存储医生信息、患者详情和已安排的预约。

模式设计旨在保持数据结构化、关系型,并在预约过程中易于查询

1. 医生表

医生表存储诊所可用医生的信息。

每条记录代表一位医生及其专业和出诊时间。这些数据帮助聊天机器人在用户选择特定专科时识别应该推荐哪位医生。

关键字段:

  • doctor_id:每位医生的唯一标识符
  • doctor_name:医生姓名
  • speciality:医疗专科(皮肤科、儿科等)
  • office_timing:医生的可用工作时间

对于这个问题陈述,我们选择了5个专科:

  • 皮肤科
  • 骨科
  • 全科医生
  • 儿科
  • 耳鼻喉科专家

我在下表中为每位医生虚构了姓名和出诊时间。欢迎扩展到更多专科。

医生表(图片来源:作者)

2. 患者表

患者表存储预约所需的患者信息。

每当用户预约时,系统检查该患者是否已存在(基于电话号码)。如果不存在,则创建新的患者记录。

关键字段:

  • customer_id:患者的唯一标识符
  • name:患者姓名
  • phone:患者联系电话

3. 预约表

预约表是存储预约详情的核心表。每次预约连接一位医生和一位患者在特定日期和时间。

关键字段:

  • booking_id:每次预约的唯一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", "Dr. Anil Sharma", "全科医生", "10:00-14:00"),
        ("D2", "Dr. Neha Verma", "皮肤科医生", "11:00-16:00"),
        ("D3", "Dr. Rohit Mehta", "骨科医生", "09:00-13:00"),
        ("D4", "Dr. Kavita Rao", "儿科医生", "10:00-15:00"),
        ("D5", "Dr. Sanjay Iyer", "耳鼻喉科专家", "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

这将创建包含3个表的clinic.db。

5、服务层(智能体的工具)

服务层包含将智能体连接到数据库的业务逻辑。

在基于智能体的架构中,这些服务可以被视为智能体用来执行操作的工具,而智能体本身专注于管理对话流程。

医生服务 (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"
    
    返回:
        字符串如"13:00"
    """
    time_part, suffix = slot_str.split(" ")
    hour, minute = time_part.split(":")
    hour = int(hour)
    
    if suffix == "下午" and hour != 12:
        hour += 12
    elif suffix == "上午" and hour == 12:
        hour = 0
    
    return f"{hour:02d}:{minute}"

预约服务 (services/booking_service.py)

预约服务处理客户管理和预约操作。其任务是:

  • 使用电话号码创建新客户记录或检索现有客户
  • 检查医生在特定日期已预约的时间段
  • 过滤并返回可用时间段以供预约
  • 生成唯一预约ID并将预约存储在数据库中
  • 确认并保存最终预约详情(医生、患者、日期、时间)
# 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]  # 返回customer_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"
        appointment_date: 可选日期,格式YYYY-MM-DD。默认为今天。
    
    返回:
        预约ID
    """
    # 获取或创建客户
    customer_id = get_or_create_customer(customer_name, customer_phone)
    
    # 生成预约ID
    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"
)
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

太好了!!我们的服务运行正常!!

现在让我们进入最关键的部分:智能体层

6、智能体层

聊天机器人的核心是基于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]  # 确认ID
    available_options: List[str]  # UI选项

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": []
    }

LLM辅助函数

我们现在定义call_llm函数,它提供与OpenAI模型通信的集中方式。

关键职责:

  • 向语言模型发送系统和用户提示
  • 返回智能体使用的生成响应
  • 保持LLM交互在智能体代码中的一致性和可重用性
# 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:
    """
    所有LLM调用的集中辅助函数。
    返回助手的响应
    """
    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"LLM调用错误: {e}")
        return ""

智能体节点

代表在工作流中处理特定任务的各个步骤。

关键职责:

  • 管理问候、选择专科、确认预约等阶段
  • 处理用户输入并更新对话状态
  • 使聊天机器人工作流保持模块化和易于管理

在智能体中,我们定义多个节点,每个处理特定阶段:

  1. greeting_node:欢迎用户并询问是否要预约
  2. select_speciality_node:显示可用专科
  3. select_doctor_node:显示所选专科的医生
  4. select_date_node:让用户选择日期
  5. select_slot_node:显示可用时间段
  6. confirm_node:请求确认
  7. collect_details_node:收集客户姓名和电话
  8. completed_node:确认预约
  9. cancelled_node:处理取消

构建图

现在让我们通过将智能体节点连接成状态驱动的对话流来构建LangGraph工作流。

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"
        }
    )
    
    # 其他节点的类似条件边...
    
    # 到END的最终边
    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

这将把langgraph_flow.py保存在agents文件夹中:

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)
    
    # 更新available_options并确保消息在历史记录中
    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, "我想要预约", thread_id="session_1")
print(state["available_options"])

在您的终端中请运行以下命令:

cd clinic-agent
python test/test_agent.py

它应该能够在终端中显示可用选项。

现在让我们专注于使用streamlit的UI。

7、Streamlit UI

Streamlit界面为用户提供美观、交互式的聊天体验。

特性:

  • 实时聊天界面
  • 可点击的选项按钮
  • 带唯一ID的会话管理
# ui/chat_ui.py - Streamlit聊天机器人界面

"""诊所预约聊天机器人的Streamlit UI。"""

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
    )
    
    # 重新运行以更新UI
    st.rerun()

def run_chat_ui():
    """运行聊天UI。"""
    # 页面配置
    st.set_page_config(
        page_title="CarePlus诊所 - 预约",
        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("🏥 CarePlus诊所")
    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()

8、应用程序入口点

现在我们定义app.py,它充当启动整个聊天机器人应用程序的起点。

# app.py - 应用程序的主入口点

from ui.chat_ui import run_chat_ui

if __name__ == "__main__":
    run_chat_ui()

运行聊天机器人:两种方法。

方法1:Streamlit Web应用程序

运行聊天机器人最简单、最用户友好的方式。

要运行应用程序,请在终端中运行以下命令:

streamlit run app.py

聊天机器人将在http://localhost:8501打开

我们的预约聊天机器人的Streamlit应用界面

方法2: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
)

然后我们在笔记本中构建图并可视化:

# 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))

现在我们在笔记本中运行交互式聊天机器人会话。

# 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()立即启动'greeting'节点
        graph.invoke(create_initial_state(), config=config)
    
    print("---⚕⚕ 启动CarePlus预约会话 ---")
    
    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)
笔记本中的诊所智能体聊天机器人

请注意,在笔记本中,输入框将在笔记本顶部打开,如上面的截图所示。

我已将clinic-agent.ipynb笔记本放在仓库中,可用于运行整个流程。

9、结束语

在本文中,我们使用LangGraph、OpenAI GPT、SQLite和Streamlit构建了一个AI驱动的诊所预约聊天机器人。通过结合状态驱动的智能体工作流和对话界面,系统可以引导用户完成整个预约流程——从选择医疗专科到确认预约。分层架构将数据库、服务、智能体逻辑和UI分离,使应用程序模块化、可维护且易于扩展。

这种方法展示了对话式AI如何简化预约安排等实际工作流,同时减少手动管理工作。该系统可以进一步增强,添加日历集成、通知、多语言支持或消息平台集成等功能。总的来说,这个项目作为如何将现代AI框架和简单Web工具结合以构建智能、用户友好的医疗应用程序的实际示例。


原文链接: Agentic AI Project: Build a Customer Service Chatbot for a Clinic

汇智网翻译整理,转载请标明出处