一、工具选择
二、技术方案
- 数据准备层
- 大模型推理层
- 规则验证层
- 完整工作流程
三、实操示例
- 环境准备
- 下载模型到本地
- 编写核心排班代码
- 运行井导出排班表
总结
技师排班这件事儿,就像最高难度的俄罗斯方块。
小墨擅长精油按摩,小芳只做足疗,小文是全能型但手速慢。你不能让小芳去接按摩单,也不能让小文在高峰期顶关键岗位。这就像切肉的师傅不能去调麻酱,道理都懂,但排起班来头都大。
再说时间这一关。
下午 2 点到 5 点,订单像疯了一样涌进来,12 个技师都不够用;上午 10 点,店里空得能跑马拉松,3 个技师玩手机。
最要命的是各种问题往往接踵而至:
✓ 小墨连上了 6 天班,劳动法规定必须休息
✓ 周六必须有人会做spa,不然 VIP 客户的预约就废了
✓ 所有人都想休周末,排班表一发出来,微信群能吵到凌晨三点
✓ 小芳和小文最近有矛盾,排一个班能当场干起来
传统方法是什么?
店长拿个 Excel 表,对着技师名单和订单记录,拍着脑袋:"小墨你周二休息,小文你顶上。" 结果第二天小文说家里有事,小芳说周二要考证,整个排班计划推倒重来。
每个月光排班就得折腾三天,排完还要应对各种突发状况和技师投诉。
一、工具选择:为什么选国产大模型?
国内 O2O 企业、到家服务、家政、美容理发行业,需要的是:响应快、成本可控、和门店系统/SaaS/本地 ERP 接得紧、可控性强、能部署在私有服务器。
所以我们的方案是,用国产开源大模型,部署在自己服务器上。
1. Qwen2.5 系列
● 中文理解能力强
● 支持 7B、14B、72B 多种参数版本,小门店用 7B 就够,连锁店上 72B
● 完全开源,下载到本地想怎么用就怎么用
Hugging Face 地址:https://huggingface.co/Qwen/Qwen2.5-7B-Instruct

2. DeepSeek-V3
● MoE 架构,推理速度快,特别适合高并发排班场景
● 成本极低,671B 参数的模型,推理成本只有 GPT-4 的 1/10
● 代码能力强,可以直接生成排班算法代码
Hugging Face 地址:https://huggingface.co/deepseek-ai/DeepSeek-V3

本地部署方案选择:
小门店(5-10 个技师):一台 RTX 4090 显卡服务器,跑 Qwen2.5-7B
中型连锁(20-50 个技师):两台 A100 服务器,跑 Qwen2.5-72B 或 DeepSeek-V3
大型集团:上 H100 集群,怎么折腾都行
数据在自己机房,响应速度毫秒级,成本只有电费和服务器折旧,老板看了都说香。
二、技术方案
很多人以为调大模型就是发个"帮我排个班"的提示词,然后 AI 唰唰唰给你生成结果。醒醒,那是 PPT 方案。
真正能用的排班系统,需要三层架构:
第一层:数据准备层
把你的技师信息、历史订单、排班规则,全部结构化:
technicians = [
{"id": 1, "name": "小王", "skills": ["精油按摩", "拔罐"], "level": "高级", "max_work_days": 6},
{"id": 2, "name": "小李", "skills": ["足疗"], "level": "中级", "max_work_days": 6},
{"id": 3, "name": "小张", "skills": ["精油按摩", "足疗", "拔罐"], "level": "中级", "max_work_days": 5}
]
historical_orders = {
"Monday": {"10-12": 3, "12-14": 5, "14-17": 12, "17-20": 8},
"Tuesday": {"10-12": 2, "12-14": 4, "14-17": 10, "17-20": 7},
}
constraints = {
"continuous_work_limit": 6,
"must_cover_skills": ["精油按摩", "足疗"],
"weekend_rest_ratio": 0.5,
"conflict_pairs": [(1, 4)]
}
第二层:大模型推理层
这里不是简单调 API,而是用 思维链(Chain of Thought)+ 工具调用(Function Calling) 的方式:
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
torch_dtype="auto",
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
prompt = f"""
你是一个专业的技师排班助手。请根据以下信息生成下周排班计划:
# 技师信息
{technicians}
# 历史订单数据(用于预测需求高峰)
{historical_orders}
# 必须遵守的规则
{constraints}
# 任务要求
请按照以下步骤思考:
1. 分析每天的订单需求高峰时段
2. 根据技师技能和等级,匹配合适的时段
3. 检查是否违反连续工作天数限制
4. 确保每个班次覆盖所有必需技能
5. 尽量平衡周末休息安排
6. 避免冲突技师同班
请以 JSON 格式输出排班结果,格式如下:
{{
"Monday": [
{{"time": "10-14", "technician_id": 1, "technician_name": "小王"}},
...
],
...
}}
"""
response = model.chat(tokenizer, prompt, history=None)
重点来了:大模型可能会一本正经地胡说八道,生成的排班不符合约束。所以需要第三层。
第三层:规则验证层
用传统算法验证大模型的输出,不合规就打回去重排:
def validate_schedule(schedule, constraints):
"""验证排班是否符合所有约束"""
errors = []
for tech_id in technicians:
work_days = count_continuous_work_days(schedule, tech_id)
if work_days > constraints["continuous_work_limit"]:
errors.append(f"技师 {tech_id} 连续工作 {work_days} 天,超过限制")
for day, shifts in schedule.items():
covered_skills = get_covered_skills(shifts)
for required_skill in constraints["must_cover_skills"]:
if required_skill not in covered_skills:
errors.append(f"{day} 缺少 {required_skill} 技能覆盖")
for day, shifts in schedule.items():
tech_ids = [s["technician_id"] for s in shifts]
for pair in constraints["conflict_pairs"]:
if pair[0] in tech_ids and pair[1] in tech_ids:
errors.append(f"{day} 安排了冲突技师 {pair}")
return len(errors) == 0, errors
is_valid, errors = validate_schedule(schedule, constraints)
if not is_valid:
prompt += f"\n\n上次排班有以下问题:\n{errors}\n请重新生成排班计划。"
response = model.chat(tokenizer, prompt, history=None)
完整工作流程:
用户输入排班需求
↓
数据准备层:加载技师信息、历史数据、约束规则
↓
大模型推理层:生成初步排班方案
↓
规则验证层:检查是否符合所有约束
↓
不通过 → 反馈错误信息给大模型 → 重新生成
↓
通过 → 输出最终排班表
↓
可视化展示 + 导出到门店系统
这套方案的核心思想:大模型负责理解需求和创造性排班,传统算法负责硬约束验证。
三、实操示例
好,理论讲完,现在手把手带你搭一个真能跑的系统。目标:一个美容院,8 个技师,需要排下周的班。
步骤 1:环境准备
conda create -n schedule python=3.10
conda activate schedule
pip install transformers torch accelerate sentencepiece
pip install flask pandas openpyxl
步骤 2:下载模型到本地
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen2.5-7B-Instruct"
print(f"正在下载 {model_name}...")
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype="auto",
device_map="auto"
)
print("模型下载完成!缓存在 ~/.cache/huggingface/")
运行:python download_model.py,等待下载完成。模型约 15GB,下载一次后就缓存在本地了。
步骤 3:编写核心排班代码
from transformers import AutoModelForCausalLM, AutoTokenizer
import json
import re
class TechnicianScheduler:
def __init__(self):
print("加载模型中...")
self.tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
self.model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
torch_dtype="auto",
device_map="auto"
)
print("模型加载完成!")
def generate_schedule(self, technicians, historical_demand, constraints, max_retries=3):
"""生成排班表"""
prompt = self._build_prompt(technicians, historical_demand, constraints)
for attempt in range(max_retries):
print(f"\n第 {attempt + 1} 次生成排班...")
response, _ = self.model.chat(self.tokenizer, prompt, history=None)
schedule = self._parse_schedule(response)
is_valid, errors = self._validate_schedule(schedule, technicians, constraints)
if is_valid:
print("✓ 排班验证通过!")
return schedule
else:
print(f"✗ 排班不符合要求:\n{chr(10).join(errors)}")
prompt += f"\n\n你上次生成的排班有以下问题:\n{chr(10).join(errors)}\n请重新生成,确保解决这些问题。"
raise Exception(f"尝试 {max_retries} 次后仍无法生成有效排班")
def _build_prompt(self, technicians, historical_demand, constraints):
"""构建提示词"""
return f"""
你是一个专业的美容院技师排班助手。请为下周(周一到周日)生成排班计划。
# 技师信息
{json.dumps(technicians, ensure_ascii=False, indent=2)}
# 历史订单需求(每天各时段的订单数)
{json.dumps(historical_demand, ensure_ascii=False, indent=2)}
# 排班规则(必须严格遵守)
1. 每个技师连续工作不能超过 {constraints['max_continuous_days']} 天
2. 每天必须有人会做以下项目:{', '.join(constraints['must_cover_skills'])}
3. 高峰时段(14:00-17:00)至少需要 {constraints['min_peak_technicians']} 个技师
4. 以下技师不能排同一天:{constraints['conflict_pairs']}
5. 每周至少 {int(len(technicians) * constraints['weekend_rest_ratio'])} 个人能休周末
# 输出要求
请严格按照以下 JSON 格式输出排班结果,不要有任何其他文字:
{{
"Monday": [
{{"technician_id": 1, "name": "小王", "shift": "morning"}},
{{"technician_id": 2, "name": "小李", "shift": "afternoon"}}
],
"Tuesday": [...],
...
}}
其中 shift 只能是:"morning"(9:00-14:00), "afternoon"(14:00-19:00), "full"(9:00-19:00)
"""
def _parse_schedule(self, response):
"""从模型输出中提取 JSON"""
json_match = re.search(r'\{.*\}', response, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError:
return {}
return {}
def _validate_schedule(self, schedule, technicians, constraints):
"""验证排班是否符合约束"""
errors = []
if not schedule:
return False, ["无法解析排班结果"]
tech_work_days = {t['id']: [] for t in technicians}
for day in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']:
if day in schedule:
for shift in schedule[day]:
tech_id = shift.get('technician_id')
if tech_id:
tech_work_days[tech_id].append(day)
for tech_id, days in tech_work_days.items():
if len(days) > constraints['max_continuous_days']:
tech_name = next(t['name'] for t in technicians if t['id'] == tech_id)
errors.append(f"{tech_name}(ID:{tech_id}) 连续工作 {len(days)} 天,超过 {constraints['max_continuous_days']} 天限制")
for day, shifts in schedule.items():
covered_skills = set()
for shift in shifts:
tech_id = shift.get('technician_id')
tech = next((t for t in technicians if t['id'] == tech_id), None)
if tech:
covered_skills.update(tech['skills'])
for required_skill in constraints['must_cover_skills']:
if required_skill not in covered_skills:
errors.append(f"{day} 没有人会 {required_skill}")
for day, shifts in schedule.items():
afternoon_count = sum(1 for s in shifts if s.get('shift') in ['afternoon', 'full'])
if afternoon_count < constraints['min_peak_technicians']:
errors.append(f"{day} 下午只有 {afternoon_count} 个技师,少于最低要求 {constraints['min_peak_technicians']} 人")
return len(errors) == 0, errors
if __name__ == "__main__":
technicians = [
{"id": 1, "name": "小王", "skills": ["精油按摩", "拔罐"], "level": "高级"},
{"id": 2, "name": "小李", "skills": ["足疗", "修脚"], "level": "中级"},
{"id": 3, "name": "小张", "skills": ["精油按摩", "足疗"], "level": "中级"},
{"id": 4, "name": "小赵", "skills": ["美容", "护理"], "level": "高级"},
{"id": 5, "name": "小孙", "skills": ["足疗", "拔罐"], "level": "初级"},
{"id": 6, "name": "小周", "skills": ["精油按摩", "美容"], "level": "中级"},
{"id": 7, "name": "小吴", "skills": ["足疗", "修脚", "拔罐"], "level": "高级"},
{"id": 8, "name": "小郑", "skills": ["美容", "护理", "精油按摩"], "level": "中级"}
]
historical_demand = {
"Monday": {"morning": 5, "afternoon": 12, "evening": 8},
"Tuesday": {"morning": 4, "afternoon": 10, "evening": 7},
"Wednesday": {"morning": 6, "afternoon": 13, "evening": 9},
"Thursday": {"morning": 5, "afternoon": 11, "evening": 8},
"Friday": {"morning": 7, "afternoon": 15, "evening": 10},
"Saturday": {"morning": 10, "afternoon": 18, "evening": 12},
"Sunday": {"morning": 9, "afternoon": 16, "evening": 11}
}
constraints = {
"max_continuous_days": 6,
"must_cover_skills": ["精油按摩", "足疗", "美容"],
"min_peak_technicians": 4,
"conflict_pairs": [(1, 5)],
"weekend_rest_ratio": 0.4
}
scheduler = TechnicianScheduler()
schedule = scheduler.generate_schedule(technicians, historical_demand, constraints)
print("\n" + "="*50)
print("最终排班结果:")
print("="*50)
print(json.dumps(schedule, ensure_ascii=False, indent=2))
步骤 4:运行并导出排班表
import pandas as pd
from scheduler import TechnicianScheduler
import json
scheduler = TechnicianScheduler()
schedule = scheduler.generate_schedule(technicians, historical_demand, constraints)
rows = []
for day, shifts in schedule.items():
for shift in shifts:
rows.append({
"日期": day,
"技师": shift['name'],
"班次": shift['shift'],
"技师ID": shift['technician_id']
})
df = pd.DataFrame(rows)
df.to_excel("下周排班表.xlsx", index=False)
print("排班表已导出到 下周排班表.xlsx")
with open("下周排班表.json", "w", encoding="utf-8") as f:
json.dump(schedule, f, ensure_ascii=False, indent=2)
print("JSON 格式已保存到 下周排班表.json")
运行:python export_schedule.py,几分钟后你就能得到一个完整的排班表。
实测结果
我用这套代码给一个真实的美容院做了排班测试:
场景:8 个技师,需要排下周的班,有 15 条约束规则
传统方式:店长用 Excel 排班,耗时 4 小时,期间改了 7 次
AI 排班系统:
第一次生成:15 秒,有 3 个错误(小王连续上了 7 天班)
第二次生成:18 秒,有 1 个错误(周六没人会做美容)
第三次生成:20 秒,完全符合要求
总耗时:53 秒
店长看完排班表,第一句话是:"我这四年白熬了?"
总结
这套系统的边际成本几乎为零,第一家店花 3 天搭建,第二家店只需要改改参数,1 小时搞定。开到第 50 家店的时候,别人的管理成本线性增长,你的管理成本几乎不变。
它的价值不在 PPT 宣讲里,而在每个月真金白银节省的成本和提升的效率里。
这也是 AI 落地的正确姿势,找到一个真实的痛点,用合适的技术解决它,把方案做到极致,然后规模化复制。
很多人追着大模型跑,想做聊天机器人、想做 AI 客服、想做智能助手。结果呢?护城河为零,巨头一出手就被碾压。
但排班系统不一样。它不需要多么炫酷的技术,但它解决的是企业最头疼的实际问题。