很多人用 AI 写旅游攻略,得到的结果是"上午参观博物馆,下午漫步老城区"这种看着对、用起来空的文字。问题不在模型,而在于把"写攻略"当成了写作文,而不是生成一份可执行的结构化数据。
这篇教程做一件事:输入一个目的地和几天时间,输出一份能直接照着走的行程表——每天几点到几点、去哪、怎么去、大概多久、哪些信息需要出发前再确认。沿途会给出可直接照抄的提示词模板、数据结构和校验脚本。
这篇能做出什么
完成后你会得到三样东西:
1. 一份标准的行程 JSON,字段固定,便于后续加工;
2. 一份 Markdown 版行程表,能直接发到群里或存进备忘录;
3. 一个自动校验脚本,能提前发现时间冲突、一天塞太满、跨城来回跑这类低级错误。
整条流程是:把模糊需求变成结构化输入 → 让模型只做它擅长的事(聚类、排序、给备选)→ 用脚本做机械校验 → 用地图和官方页面补真实信息。模型负责"编排",真实数据必须来自权威渠道。
前置条件清单
- 一个能调用大模型 API 的账号和密钥,模型名、接口地址以所用服务商的官方文档当前版本为准。
- Python 3.9 以上环境,能联网安装依赖。
- 基础的命令行操作能力,会设置环境变量即可。
- 出行日期、天数、同行人构成、预算量级、必去和必不去的地点。
- 一个地图应用和一个搜索引擎,用来核对景点是否开放、车程是否合理。
安装依赖:
```bash
pip install openai
```
设置密钥(不要把密钥写进代码文件):
```bash
export OPENAI_API_KEY="你的密钥"
export OPENAI_BASE_URL="你的接口地址(如服务商要求)"
```
OPENAI_BASE_URL 是否需要设置,取决于所用服务商,以官方文档当前版本为准。
步骤一:把模糊需求写成结构化输入
先别急着写提示词。把需求整理成一段固定格式的文本,后面所有环节都复用这个结构:
```yaml
目的地: 京都
出行日期: 2025-04-05 至 2025-04-08
天数: 4
同行人: 两位成人,一位 65 岁长辈,步行耐力一般
节奏: 每天 2 到 3 个主要景点,下午留 1 小时机动
住宿区域: 京都站附近
必去: 伏见稻荷、岚山竹林
不去: 需要长时间爬山的路线、夜间酒吧街
预算量级: 中等,不追求高档餐厅
饮食限制: 不吃生食
其他: 不接受一天换两次住宿
```
这段内容越具体,后面返工越少。"步行耐力一般"这种描述比"轻松一点"有用得多,模型能据此把坡道和长距离步行排除掉。
步骤二:定义行程的数据结构
先定字段,再让模型往里填。这样输出的东西天然可校验、可渲染。
```json
{
"destination": "京都",
"days": [
{
"date": "2025-04-05",
"theme": "抵达与市区适应",
"items": [
{
"start": "14:00",
"end": "16:00",
"title": "景点或活动名称",
"area": "所在区域",
"transport": "从上一站过来的方式与大致耗时",
"duration_min": 120,
"tip": "需要注意的一句话",
"need_confirm": true
}
],
"notes": "当天整体提醒"
}
]
}
```
几个字段是刻意加的:
area用来做地理聚类,避免一天在东边一天在西边来回横跳;duration_min让脚本能算总时长;need_confirm标记哪些信息必须出发前自己核实,比如开放时间、是否需预约;transport只写方式和量级,具体车次以地图应用为准。
步骤三:写系统提示词
把规则写死在AI 词典:系统提示词">系统提示词里,用户消息只放具体目的地信息。提示词模板建议存成单独文件,方便复用和迭代。
```text
你是一名行程编排助手。你的任务是把目的地需求整理成结构化行程,不是写散文。
硬性规则:
1. 只输出 JSON,不要输出任何解释、前言、Markdown 代码块标记。
2. 严格遵循给定的 JSON 结构,字段名不要改,不要新增字段。
3. 每天安排 2 到 3 个主要项目,项目之间按地理位置聚类,同一区域的项目放在同一天。
4. 时间使用 24 小时制 HH:MM,同一天的项目时间不得重叠,且按时间升序排列。
5. 不要编造具体地址、电话、票价、营业时间。凡是这类信息,把 need_confirm 设为 true,
并在 tip 里写"出发前请核实开放时间"。
6. 不确定的景点或店铺,宁可不写,也不要凭空生成名称。
7. 考虑同行人的体力,连续两天不要安排强度相近的远距离路线。
8. 全部用中文输出,专有名词可保留原文。
输出格式:{"destination": "...", "days": [...]}
```
第 5、6 条很关键。模型对具体营业时间的记忆经常滞后,让它主动标"待确认",比让它编一个像真的时间更安全。
步骤四:调用模型生成行程
下面是一个可直接运行的脚本,读取提示词文件与需求文件,输出 JSON。
```python
import json
import os
from pathlib import Path
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ.get("OPENAI_BASE_URL"),
)
MODEL = "以所用服务商官方文档当前推荐的模型为准"
system_prompt = Path("system_prompt.txt").read_text(encoding="utf-8")
trip_input = Path("trip.yaml").read_text(encoding="utf-8")
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"请根据以下需求生成行程:\n{trip_input}"},
],
temperature=0.4,
response_format={"type": "json_object"},
)
raw = resp.choices[0].message.content
Path("itinerary_raw.json").write_text(raw, encoding="utf-8")
print("已保存,长度:", len(raw))
```
说明几点:
response_format是否支持、字段名如何写,以所用服务商官方文档当前版本为准。不支持时,去掉该参数,改用下面的清洗函数。temperature设低一些,行程编排属于结构化任务,不需要发挥。- 把原始输出单独存一份文件,出问题时可回溯,不用重新花钱调接口。
如果模型把 JSON 包在代码块或解释文字里,用这个函数兜一下:
```python
import json
import re
def extract_json(text: str) -> dict:
text = text.strip()
if text.startswith("```"):
text = re.sub(r"^```[a-zA-Z]*\n?", "", text)
text = re.sub(r"\n?```$", "", text)
start = text.find("{")
end = text.rfind("}")
if start == -1 or end == -1:
raise ValueError("未找到 JSON 内容")
return json.loads(text[start:end + 1])
```
步骤五:写校验脚本,别信自己的眼睛
模型输出的行程一定有细节问题,靠肉眼看不出来。用脚本做机械校验,几秒钟跑完。
```python
import json
from datetime import datetime
def to_min(hhmm: str) -> int:
t = datetime.strptime(hhmm, "%H:%M")
return t.hour * 60 + t.minute
def check(path: str) -> None:
data = json.load(open(path, encoding="utf-8"))
problems = []
for day in data["days"]:
date = day["date"]
items = day["items"]
1. 时间是否重叠
for a, b in zip(items, items[1:]):
if to_min(a["end"]) > to_min(b["start"]):
problems.append(f"{date} 时间重叠:{a['title']} 与 {b['title']}")
2. 单日总时长是否超载
busy = sum(to_min(i["end"]) - to_min(i["start"]) for i in items)
if busy > 9 * 60:
problems.append(f"{date} 安排过满,共 {busy // 60} 小时")
3. 是否跨区域太频繁
areas = [i["area"] for i in items]
if len(set(areas)) > 2:
problems.append(f"{date} 涉及 {len(set(areas))} 个区域,建议合并")
4. 待确认项统计
todo = [i["title"] for i in items if i.get("need_confirm")]
if todo:
print(f"[{date}] 需出发前核实:{'、'.join(todo)}")
if problems:
print("发现问题:")
for p in problems:
print(" -", p)
else:
print("基础校验通过,仍需人工核对开放信息。")
check("itinerary_raw.json")
```
这个脚本只做四件事:时间不重叠、单日不超载、区域不散、把待确认项列出来。发现"某天涉及 4 个区域"这类问题,直接回到提示词里补一条"每天区域不超过 2 个",重新生成。
步骤六:渲染成能用的行程表
JSON 自己看很累,转成 Markdown 表格。下面这段代码不依赖额外库。
```python
import json
def render(path: str) -> str:
data = json.load(open(path, encoding="utf-8"))
out = [f"# {data['destination']} 行程\n"]
for day in data["days"]:
out.append(f"## {day['date']} {day.get('theme', '')}\n")
out.append("| 时间 | 安排 | 区域 | 交通 | 备注 |")
out.append("| --- | --- | --- | --- | --- |")
for i in day["items"]:
flag = "(待核实)" if i.get("need_confirm") else ""
out.append(
f"| {i['start']}-{i['end']} | {i['title']}{flag} | {i['area']} "
f"| {i['transport']} | {i['tip']} |"
)
if day.get("notes"):
out.append(f"\n> {day['notes']}\n")
return "\n".join(out)
md = render("itinerary_raw.json")
open("行程.md", "w", encoding="utf-8").write(md)
print(md)
```
生成后建议再做一件事:把每天的行程单独复制到日历应用或备忘录,出行时离线也能看。JSON 文件留在电脑里,临时改行程时改 JSON 再重新渲染,比手改 Markdown 省事。
步骤七:把"待确认"逐条落实
这是最容易被跳过、却决定攻略能不能用的一步。带着 need_confirm 列表,按顺序核对:
1. 景点是否开放、是否需要预约、有无闭馆日。以景区或场馆官方页面为准。
2. 交通耗时用地图应用实测一次,不要用模型给的时间。
3. 门票、交通卡、当地交通规则,以官方渠道的信息为准。
4. 天气与季节因素。把出行日期的天气情况重新贴回提示词,让它调整室内外安排。
核对完,把 need_confirm 改成 false,或者把确认到的信息写进 tip。这份 JSON 就成了真正可执行的行程。
常见坑与排错
坑一:模型编出不存在的景点或店铺。
症状是名字听起来很合理,搜索查不到。对策是在提示词里明确"不确定就不写",并且对每个地名都搜一次。地名核对是人工环节,不要指望模型自查。
坑二:营业时间、票价写成具体数字。
这类信息变化快,模型给的多半是旧值。对策是提示词里禁止输出具体时间与价格,统一标"待核实"。
坑三:一天排了六个景点,看着很赚,实际走不动。
对策是用校验脚本卡单日总时长上限,并在提示词里写明同行人构成。带长辈和带孩子是两种完全不同的排法。
坑四:地理上东一个西一个。
对策是把"每天区域不超过 2 个"写进硬性规则,并用校验脚本检查。注意模型给的车程往往偏乐观,实际以地图应用为准。
坑五:输出被截断或夹带解释文字。
长行程容易超出单次输出上限。对策是按天拆分生成:先让模型给整体框架,再逐天生成细节,最后合并 JSON。
坑六:JSON 解析失败。
先看原始输出有没有被代码块包住,用前面的 extract_json 处理;仍然失败就降低温度重新生成,或要求模型"只输出 JSON"。
坑七:把一个目的地直接换成另一个,结果全是套话。
不同城市的交通结构差别很大。换目的地时,把当地交通方式、是否需要租车、景点分布特征补充进输入,不要只改地名。
下一步建议
跑通基础流程后,可以往几个方向延伸:
- 加预算表:让模型只输出"消费项类别 + 量级",具体金额留空,出行前按官方渠道填写。
- 多方案对比:同一目的地生成"紧凑版""轻松版"两套行程,把校验结果作为选择依据。
- 接入地图服务:用地图接口替换模型估算的车程,这一步能把行程的可用性提升一个档次。接口用法以对应服务商官方文档当前版本为准。
- 做成模板库:把不同场景(亲子、长辈同行、单人短途、出差顺路游)的输入模板存下来,下次只改日期和目的地。
- 沉淀复盘:行程走完后,把实际耗时和计划耗时的差异记回 JSON,下次生成时作为约束条件喂给模型。
核心思路只有一句:让模型做它擅长的编排与聚类,把事实核查交给官方渠道和地图应用。只要守住这条边界,AI 写出来的就不只是攻略,而是一份能直接照着走的行程。
