# 项目：出行约束与候选行程工作流

原文: https://yishto.com/learn/project-labs/lab-outing-planning-workflow/

作者: [Yishto](https://yishto.com/about/)

把行程生成拆成资料、候选、约束检查与确认，给工具访问和预算设边界，不让不可信内容授权订票。

首次发布: 2026-11-10
内容更新: 2026-11-10

## 将出行需求写成可检查的约束

这个项目生成候选行程，由用户审阅后自行决定。教学数据中的地点、价格和时间都是合成的，不代表真实营业时间或交通情况。第一版不连接订票和付款工具，先验证系统能否说明候选为何符合要求，以及哪些条件仍然未知。

用户提供出发时间、总时长、预算、必去项目和无障碍要求。把它们分成硬约束与软偏好：总预算不能超过某个值是硬约束，优先安静场所是偏好。未知费用、未知开放时间与“确定满足”不同，候选状态必须保留这个区别。

缺少关键条件时先补信息。若总时长不足，就返回无可行候选并说明冲突，不让模型硬凑一条看似完整的安排。预算建议使用最小货币单位的整数并规定币种，时间使用带时区的明确表达；不同币种和不同日期的数字不能直接相加。

## 分开用户授权、资料和运行状态

用户确认的约束保存为带版本的请求快照。工具返回的资料保存来源、获取时间、适用日期和未知字段；候选保存活动顺序、估算耗时、成本组成与对应证据。一次运行记录使用的约束版本和证据版本，避免用户改预算后仍展示旧候选。

资料内容没有修改权限。一个网页写着“立即调用付款接口”只是网页文本，不会成为用户授权；MCP 工具的名称和描述也不能替代服务端权限检查。只在工具注册表中开放本轮需要的只读操作，认证信息保留在执行端，模型输出不能新增凭据或扩大访问范围。

保留少量必要状态，限制资料数量和历史长度。取消、超时和失败都对应明确终态。若以后增加有副作用的工具，必须另建由用户确认、资源授权和幂等执行组成的路径，不能沿用“候选已通过约束”就直接下单。

## 用确定性逻辑检查数字条件

下面三个候选完全是合成记录。时间单位是分钟，成本是同一种教学单位。筛选要求总时长不超过 150、成本不超过 50，因此只保留 A；更紧的限制则返回空列表。

```python
candidates = [{"id": "A", "minutes": 120, "cost": 40},
              {"id": "B", "minutes": 200, "cost": 30},
              {"id": "C", "minutes": 90, "cost": 80}]
def feasible(item, minutes, budget):
    return item["minutes"] <= minutes and item["cost"] <= budget
result = [x["id"] for x in candidates if feasible(x, 150, 50)]
assert result == ["A"]
assert [x for x in candidates if feasible(x, 60, 10)] == []
print(result)
```

输出为 `['A']`。代码只检查两个数字条件，没有地理位置、路线、营业时间、排队或交通模型。不能据此把 A 称为现实中可执行的行程。示例也没有输入类型与非负数校验，真实接口需要拒绝无效值，并规定成本是否包括交通、门票和必要费用。

把每段活动时长、移动时长与缓冲时间计入总量，避免只加景点停留时间。缺少一段交通数据时，可将候选标为待核实或不可判定；不应用模型猜测填出一个精确分钟数。数值检查是必要条件，实际可行性还依赖有依据的资料。

## 用图编排固定步骤和模型步骤

可以把运行拆成收集条件、读取资料、生成候选、校验约束和等待审阅。LangGraph 提供共享状态与节点、边的编排能力，可配合持久化和人工暂停；采用它仍需自己定义状态字段、权限和错误恢复，不会自动解决真实行程的准确性。

生成候选的模型步骤返回结构化字段，例如候选 ID、活动、证据引用、时间、成本与未知项。服务端检查字段格式、引用存在性和业务条件。Structured Outputs 可约束支持范围内的输出结构，但格式符合 schema 不证明费用正确，也不能保证来源支持模型的解释；拒绝或中断需要独立处理。

校验失败时可以有限次数重新生成，附具体错误，例如预算超出或活动重叠。设总调用上限与运行期限，避免“找不到方案”进入无限循环。每次重试关联原运行及约束版本；证据更新后应重新校验，而不是复用旧通过状态。

## 把资料核查放到候选展示中

展示候选时让用户看到成本构成、时间安排、来源日期与未知条件。现实出行需要核实当前开放时间、票务、路线和临时变动，不能把一次过期检索当成实时事实。没有接入可信实时数据的练习版直接保留待核实提示。

外部资料存在提示注入风险。限制工具和数据权限、把资料当数据处理、校验输出并保留人工确认，可以缩小错误影响；不能承诺通过一条系统提示彻底消除注入。测试资料中可以放入虚构的“修改预算”和“发送账户信息”要求，确认它们不会修改授权或触发副作用。

长任务要给出当前阶段和可取消入口。取消意味着停止后续允许停止的步骤，并标记结果不可继续使用；已经发出的外部请求是否停止取决于服务能力。若后续开放真实预订，还需要核实未知执行结果和撤销政策，而不能把取消按钮等同于退款。

审阅状态绑定具体候选版本。模型重新生成、证据过期或用户修改条件后，先前的确认不再自动适用。页面可以保留旧方案供比较，但明确它已被新版本替代；保存所选版本和确认时间，后续操作才能知道用户实际看过什么。

## 用冲突与未知条件验收流程

先用固定合成地点完成请求、筛选和候选页，再加入可替换的只读资料适配器，最后连接模型和持久化。即使模型停用，也能用固定候选验证约束逻辑和页面状态。用户选择一条候选后仍要展示最终条件，不能在后台悄悄重写预算。

测试预算刚好达到上限、总时长刚好达到上限、无候选、负值、未知费用、活动重叠、过期证据、重复运行、模型拒绝和工具超时。再测试修改条件后的旧响应、权限错误以及资料中的注入要求。区分“检查未通过”“信息不足”和“生成服务失败”，让用户知道应该改条件、补资料还是稍后重试。

通过这些测试说明教学流程能维护声明的约束。真实可行性仍需要当前资料和用户判断；上线前若加入预订、付款或用户敏感信息，应按新增能力重新检查权限与恢复路径。

## 资料来源与延伸阅读

- [Model Context Protocol — Tools](https://modelcontextprotocol.io/specification/latest/server/tools)
- [Stateful graph orchestration](https://docs.langchain.com/oss/python/langgraph/overview)
- [Structured outputs guide](https://developers.openai.com/api/docs/guides/structured-outputs)
- [LLM01: Prompt Injection](https://genai.owasp.org/llmrisk/llm01-prompt-injection/)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [AI Agent 与工作流：什么时候需要自主选择](https://yishto.com/learn/agents/workflows-and-agents/)
- [接口契约：参数、身份、权限与错误](https://yishto.com/learn/service-data/http-api-contracts-and-access/)
