SlotBot 日程安排智能体
我想构建的是介于两者之间的东西 —— 一个表现得像业务主人的东西,进行真正的对话,理解对方需要什么,检查实际的日历,并相应地预订。没有菜单。没有表单。只有聊天。
微信 ezpoda免费咨询:AI编程 | AI模型微调| AI私有化部署 | Tripo 3D | Meshy AI
想象一下:你是一个经营小诊所的个体临床医生。没有前台,没有行政团队。只有你、你的病人,以及一个塞满了"嘿,你周四有空吗?"消息的 WhatsApp 收件箱,你在吃午饭、诊间间隙,或者在漫长的一天结束后的深夜回复这些消息。
理发师预约剪发、FYP 督导管理学生签到、自由顾问协调客户电话 —— 道理是一样的。业务很简单。但是调度开销呢?真的令人筋疲力尽。
这就是我想填补的差距。
本文的代码可以从这里获得。
1、真正的问题
模式总是一样的:有人发消息询问空闲时间,业务主人在脑海中交叉参考日历,回复,等待确认,然后创建事件。每天 10-20 条消息,你在本该零分钟完成的行政事务上花费了一个小时。
像 Calendly 这样的工具在人们可以从固定选项菜单中自助预订时表现出色。但许多现实世界的工作流程更加混乱。如果你的调度有细微差别怎么办?如果病人发消息说"我需要改到下周某个时间,最好是晚上"怎么办?
这需要上下文、灵活性和来回对话。没有表单能优雅地处理这个问题。
我想构建的是介于两者之间的东西 —— 一个表现得像业务主人的东西,进行真正的对话,理解对方需要什么,检查实际的日历,并相应地预订。没有菜单。没有表单。只有聊天。
我称之为 SlotBot。

2、像业务主人一样思考
在做出任何技术选择之前,让我们确立设计理念 —— 因为它推动了每一个决策。
SlotBot 不仅仅是回答调度问题。它在数字上就是业务主人。你用主人的日历配置它,它代表他们自主运行。病人不知道(也不在乎)他们是在和人还是智能体对话 —— 他们只想知道周二下午 5 点是否可用,并把它预订下来。
这种框架完全改变了需求。它不是一个查找工具。它不是一个 FAQ 机器人。它需要:
- 理解对话式、非结构化的消息
- 知道在行动之前还需要什么信息
- 真正写入日历 —— 创建、更新和取消约会,而不仅仅是建议时间段
- 根据请求检查实际的日历可用性
这是一个非平凡的流程。单个 LLM 提示无法可靠地完成所有这些。
3、为什么选择 CrewAI
我必须回答的第一个问题是,我们是否真的需要一个智能体框架。我们不能只是链接几个提示就完成了吗?
简短的回答是:一旦你的流程有记忆、决策和现实世界的操作(如写入日历),单个提示就不够了。你需要结构。你需要能够说"这一步做这个,那一步做那个,这里是它们如何相互交流" —— 而不会变成一堆粘合代码的混乱。
这就是 CrewAI 的用武之地。由 Tony Kip Kip 和 João Moura 构建,CrewAI 是一个用于构建多智能体系统的开源框架 —— 可以把它想象成一种将 AI 组织成专业工作者团队的方式,每个工作者都有明确定义的角色,为实现共同目标而合作。不是让一个模型做所有事情,而是将问题分解为专注的智能体并让它们协作。
让我特别吸引我的是它的易用性。智能体和任务在纯 YAML 文件中配置,这意味着每个智能体做什么以及为什么的逻辑与运行它的代码完全分离。你可以阅读 CrewAI 配置并在不了解底层实现的情况下理解系统。
它还有一个名为 ConditionalTask 的功能 —— 一种向流程添加分支逻辑的简洁方法。在 SlotBot 的情况下,对话中的关键问题总是:我们有足够的信息来预订,还是需要询问更多信息? CrewAI 让你能够原生地表达该决策,而不会在代码中分散混乱的 if/else 逻辑。
对于需要做出真实决策和调用真实 API 的流程,CrewAI 感觉是正确的工具。
4、架构:四个智能体,五个任务

以下是每个智能体的作用以及它作为独立实体存在的原因:
# agents.yaml
nlp_parser:
role: Natural Language Processing Specialist
goal: >
Parse user message to extract intent (booking, deleting, checking)
and any meaningful entities from the input, such as name, email, date, and time.
llm: gemini/gemini-2.5-flash-lite-preview-06-17
session_manager:
role: Session and Identity Management Coordinator
goal: >
Manage user identity, session state, and track all required information
for calendar operations
llm: gemini/gemini-2.5-flash-lite-preview-06-17
calendar_manager:
role: Calendar Operations Specialist
goal: >
Execute calendar operations including availability checks, bookings, and cancellations.
llm: gemini/gemini-2.5-flash-lite-preview-06-17
response_agent:
role: Clinical Appointment Assistant
goal: >
Serve as final point of contact, delivering clear, concise, and professional
communication about appointment status.
llm: gemini/gemini-2.5-flash-lite-preview-06-17
每个智能体有一项工作。NLP 解析器不决定对提取的信息做什么 —— 它只是提取。会话管理器不与日历对话 —— 它只是决定缺少什么。这种单一职责设计使系统大大更容易调试和扩展。
为什么这很重要: 当多智能体系统中的东西坏了时,你需要知道哪个智能体搞坏了它。每个智能体一项工作意味着有一个地方可以查看。
任务遵循顺序流程:
# tasks.yaml (abbreviated)
parse_user_input:
description: >
Analyze user's message to understand primary intent and extract
relevant entities (name, email, date, time).
Today's date is {current_date}. Resolve relative expressions like 'tomorrow'.
agent: nlp_parser
output_file: 'outputs/parsed_user_input.json'
validate_session_state:
description: >
Analyze parsed input and determine next_action:
'collect_info' if required fields are missing, 'execute_operation' if ready to book.
agent: session_manager
context:
- parse_user_input
collect_missing_information: # ConditionalTask
description: >
Generate clear, user-friendly questions to collect any missing information.
agent: response_agent
context:
- validate_session_state
execute_calendar_action: # ConditionalTask
description: >
Use BookAppointmentTool or CheckAvailabilityTool based on next_action.
agent: calendar_manager
context:
- parse_user_input
- validate_session_state
format_user_response:
description: >
Format output from previous tasks into a clear, friendly message for user.
agent: response_agent
context:
- execute_calendar_action
- collect_missing_information
分支逻辑存在于 crew 本身:
# crew.py
def should_collect_info(self, validation_output: TaskOutput) -> bool:
return self._get_next_action() == 'collect_info'
def should_execute_action(self, validation_output: TaskOutput) -> bool:
return self._get_next_action() in ['check_availability', 'execute_operation']
@task
def collect_missing_information(self) -> ConditionalTask:
return ConditionalTask(
config=self.tasks_config['collect_missing_information'],
condition=self.should_collect_info,
agent=self.response_agent()
)
@task
def execute_calendar_action(self) -> ConditionalTask:
return ConditionalTask(
config=self.tasks_config['execute_calendar_action'],
condition=self.should_execute_action,
agent=self.calendar_manager(),
context=[self.parse_user_input(), self.validate_session_state()]
)
collect_missing_information 和 execute_calendar_action 是互斥的 —— 每轮只有一个触发。format_user_response 总是最后运行,从两者中的任何一个拉取上下文。
完整流程:

用户消息 → NLP 解析器 → 会话管理器 → [收集信息 或 执行操作] → 响应智能体 → 回复
5、实际效果如何
病人发消息:"我可以预约下周二吗?"
- NLP 解析器提取:
intent = book,start_time = 2025-07-22T...,但patient_email = null - 会话管理器看到缺少电子邮件。设置
next_action = collect_info - 响应智能体回复:"当然可以!能否分享一下您的电子邮件地址,以便我确认预订?"
下一条消息:"当然,它是 patient@example.com**"
相同的流程再次运行。这次电子邮件被提取。会话管理器设置 next_action = execute_operation。日历管理器触发,调用 BookAppointmentTool,写入事件。响应智能体确认。
两轮对话。真正临床医生的零参与。

6、我真正学到了什么
在构建 SlotBot 之后,以下是我将带到下一个智能体系统中的内容:
✅ 每个智能体的单一职责 —— 一项工作,一个智能体。调试变得简单。
✅ 用 ConditionalTask** 分支,而不是 if/else** —— 保持编排层整洁;逻辑属于条件函数。
✅ 使用 Pydantic 对输出进行类型化 —— 智能体之间的原始字符串是等待发生的调试噩梦。
✅ 心理模型比技术更重要 —— "模拟"比"调度机器人"迫使了更清晰的需求。
最难的部分不是代码。这是在编写单行代码之前弄清楚系统确切需要做什么。一旦设计理念清晰,架构自然就跟随了。
汇智网翻译整理,转载请标明出处