在智能排产、物流调度以及自动化运维场景中,推理模型常被用来生成任务序列。但实际落地时,一个反复出现的故障是:模型输出的计划明明写着某车辆在 09:00 出发,却安排它在 10:30 才到达限行解除前的必经检查站,直接违反 10:00 前必须通过的时间窗。这类问题不是模型不够聪明,而是规划与调度过程没有把时间窗当成不可打破的数学约束,仅仅当成文本建议。本文将说明如何用约束满足问题(CSP)对时间窗建模,并通过可行性验证阻断违规方案。

时间窗约束被忽略的底层原因
主流推理模型在生成长程计划时,采用的是自回归式逐步推导。每一步只参考前几步的局部状态与提示词中的自然语言规则,并不会维护一个全局的时间轴变量集合。当用户说“任务 B 必须在 14:00 前完成”,模型可能只是在文本中记住这句话,却在安排前置任务 A 时给了 3 小时时长,导致链式延迟。这种处理方式本质上是把约束弱化为软提示,缺少代数层面的互斥检查。
另一个关键是资源冲突的隐性化。时间窗往往和资源占用绑定,例如某台机床在 11:00 到 12:00 保养,不可占用。推理模型如果没有显式的区间重叠判定,就会把加工任务塞进保养时段。人类调度员靠经验避开,模型却没有等价的心智模型。只有把“开始时间加时长小于窗口右端”写成不等式,才能强制搜索空间剔除违规解。
此外,很多系统把推理结果直接当最终排程,省略了后校验。即便模型偶尔写出接近合法的计划,浮点时间计算或并行分支合并也会引入偏差。缺乏独立的可行性验证模块,使得错误时间窗安排流转到执行端才暴露,成本极高。
用CSP对时间窗进行形式化建模
CSP 的核心三要素是变量、定义域和约束。针对调度问题,我们为每个任务 i 设变量 start_i 表示开始时刻,定义域通常为 [earliest, latest] 的离散或连续区间。任务时长 dur_i 为常数。时间窗约束可表述为 start_i + dur_i <= window_end_i 且 start_i >= window_start_i。若任务间存在先后序,则增加 start_j >= start_i + dur_i。
资源排他约束写为区间不重叠:对任意两台任务占用同一机器,需满足 start_i + dur_i <= start_j 或 start_j + dur_j <= start_i。这些约束组合后,利用弧一致性算法(如 AC-3)可提前删掉变量定义域中明显无解的值。例如某任务窗口右端为 10,时长 4,则 start_i 定义域上界被剪枝到 6,模型无需枚举 7、8、9。
下面给出一个 Python 片段,用简化版 CSP 描述带时间窗的任务,并做基础过滤:
from typing import Dict, Tuple
# 任务定义:名称 -> (时长, 时间窗开始, 时间窗结束)
tasks: Dict[str, Tuple[int, int, int]] = {
'A': (2, 8, 12),
'B': (3, 9, 14),
'C': (2, 13, 16)
}
# 顺序依赖:B 必须在 A 完成后开始
deps = [('A', 'B')]
def filter_domains(tasks, deps):
domains = {}
for name, (dur, ws, we) in tasks.items():
# 初始定义域受时间窗限制
max_start = we - dur
domains[name] = list(range(ws, max_start + 1))
for pre, nxt in deps:
dur_pre = tasks[pre][0]
# 根据依赖缩减后继定义域
new_dom = []
for s in domains[nxt]:
if s >= tasks[pre][1] + dur_pre:
new_dom.append(s)
domains[nxt] = new_dom
return domains
print(filter_domains(tasks, deps))
上述代码虽未接入完整求解器,但展示了把自然语言窗口转成数值边界、再用依赖关系收敛定义域的思路。真实系统可接入 OR-Tools 或 MiniZinc,将约束交给求解器做完备搜索。
可行性验证如何拦截违规排程
可行性验证是一个独立函数,输入推理模型给出的排程字典,输出是否合法及违规原因。它不依赖模型自我反省,而是重算所有不等式。例如检查 start_i + dur_i 是否越过 window_end_i,若越过则记录“任务 X 超窗 30 分钟”。这种硬校验让任何文本层面的疏忽都无法进入执行。
验证模块还应处理资源维表的并发占用。可把机器时间轴切成片段,标记占用任务,再用扫描线算法查重叠。一旦发现重叠且无法挪动,即判定不可行,并建议调换任务顺序或外租设备。相比让模型重新生成,返回具体冲突能大幅缩短排错循环。
以下示例给出一个轻量校验逻辑,可嵌入服务接口:
def validate_schedule(schedule, tasks, machines):
# schedule: {task: start}, tasks 同上, machines: {task: mid}
for name, start in schedule.items():
dur, ws, we = tasks[name]
if start < ws or start + dur > we:
return False, f'{name} 违反时间窗'
# 机器占用冲突检查
by_machine = {}
for name, start in schedule.items():
m = machines[name]
by_machine.setdefault(m, []).append((start, start + tasks[name][0], name))
for m, spans in by_machine.items():
spans.sort()
for i in range(1, len(spans)):
if spans[i][0] < spans[i-1][1]:
return False, f'机器 {m} 上 {spans[i-1][2]} 与 {spans[i][2]} 重叠'
return True, 'ok'
schedule = {'A': 8, 'B': 10, 'C': 13}
machines = {'A': 1, 'B': 1, 'C': 2}
print(validate_schedule(schedule, tasks, machines))
将 CSP 建模与该校验串联,推理模型只负责提议,CSP 求解器与验证器负责把关,系统便能稳定输出落在时间窗内的调度。对于动态环境,还可把验证失败信息反馈给模型做下一轮提示,形成闭环而非一次生成定生死。