新闻详情

新闻详情

首页 / 资讯中心 / 详情

AI智能体+FastAPI实战:从零构建实时旅行规划系统

发布时间:2026/9/28 16:00:21来源:尧图网络
AI智能体+FastAPI实战:从零构建实时旅行规划系统
旅行规划这件事我前前后后折腾过不少方案。最早是纯手工查攻略、比价、排日程后来用表格模板再后来写脚本抓数据直到最近半年把 AI 智能体这套东西真正跑通才算把整个流程从人肉驱动变成了对话驱动。今天要聊的就是怎么用 AI 智能体把旅行规划这条链路重构一遍——从 Prompt 工程的设计到用 FastAPI 搭一个能实时查票的后端服务最后串成一个能听懂人话、能自己调工具、能给出可执行方案的完整智能体。这套东西解决的核心问题很具体传统旅行规划里信息是散的。你想去某个城市得先确定日期再查车次再比价格再看天气再排景点每一步都在不同的 App 里跳来跳去。而智能体的价值在于它能把我想下个月找个周末去周边玩两天这种模糊需求自动拆解成确定日期范围→查询可用车次→筛选时间合适的班次→结合天气推荐行程→输出完整方案这一串动作中间不需要你手动搬运任何数据。适合谁来参考如果你有一点 Python 基础了解 HTTP 接口是怎么回事想动手做一个真正能用的 AI 应用而不是停留在调 API 聊天那这篇内容就是写给你的。如果你只是想了解智能体的设计思路不打算写代码前面架构拆解和 Prompt 工程那部分也足够你理解整套逻辑。我会尽量把每个决策背后的为什么讲清楚而不是只丢一堆代码让你抄。1. 整体架构设计与技术选型思路1.1 为什么是智能体 后端服务而不是纯 Prompt 方案很多人做旅行规划的第一反应是写一个超长的 Prompt把用户需求、目的地信息、注意事项全塞进去让大模型一次性输出方案。我一开始也是这么干的实测下来问题很明显。第一个问题是数据时效性。大模型的训练数据有截止日期你问它下周六从北京到天津有哪些高铁它要么编一个看起来很像真的车次号要么直接告诉你它查不了。旅行规划里最核心的票务信息恰恰是强时效的纯 Prompt 方案在这个环节直接失效。第二个问题是计算可靠性。你让模型自己算三天两晚的行程每天安排四个景点每个景点之间通勤 40 分钟总共需要多少时间它大概率会算错。大模型做自然语言理解和生成很强但做精确计算和状态跟踪很弱。第三个问题是工具调用。真正的旅行规划需要查票、查天气、查地图距离这些都是外部工具能做的事。纯 Prompt 方案没法让模型主动去调这些工具只能靠你手动把结果贴进去那就失去了自动化的意义。所以架构上必须拆成两层智能体层负责理解意图、拆解任务、编排流程后端服务层负责提供实时、准确、可计算的数据接口。智能体通过工具调用Function Calling的方式去访问后端后端用 FastAPI 暴露标准 HTTP 接口。这样各司其职模型做它擅长的语义理解代码做它擅长的精确计算和数据获取。1.2 FastAPI 在这个架构里扮演什么角色选 FastAPI 不是因为它火而是因为它在给智能体做工具后端这个场景下确实合适。智能体调用工具的本质是发一个 HTTP 请求带上参数拿到 JSON 结果。这就要求后端框架启动快、响应快、接口定义清晰、能自动生成文档。FastAPI 基于 Starlette 和 Pydantic天然支持异步接口的请求和响应模型用 Python 类型注解就能定义还能自动生成 OpenAPI 文档——这个文档对智能体开发特别有用因为很多智能体框架支持直接读取 OpenAPI schema 来理解工具有哪些参数、返回什么结构。对比一下其他选择Flask 够轻但异步支持弱票务查询这种 IO 密集场景下并发能力吃亏Django 太重为了几个接口引入整个 ORM 和 admin 不划算Go 的 Gin 性能好但和 Python 生态的智能体框架配合起来有语言隔阂。FastAPI 刚好卡在轻量但功能完整这个位置上。1.3 智能体框架的选型考量智能体框架这两年冒出来很多我实际用过几类。一类是通用编排框架特点是灵活什么都能接但需要自己写不少胶水代码一类是低代码平台拖拽式配置上手快但深度定制受限还有一类是直接基于大模型厂商的 Function Calling 能力手写编排逻辑。我的建议是如果你要深入理解智能体工作原理从手写编排开始。用大模型的原生工具调用能力自己维护对话历史、自己解析工具调用请求、自己决定什么时候把工具结果喂回模型。这个过程会让你彻底搞明白智能体到底是怎么思考和行动的。等你把这套逻辑跑通了再去用框架就知道框架帮你省了哪些事遇到问题也知道从哪排查。这篇内容走的是手写编排路线核心循环就是用户输入→模型判断是否需要调工具→需要则执行工具→把结果返回给模型→模型继续判断→直到输出最终答案。这个循环看起来简单但里面有很多细节决定成败。2. Prompt 工程让智能体真正听懂旅行需求2.1 系统提示词的结构化设计Prompt 工程不是把话说得漂亮而是把模型的输出空间约束到你想要的范围内。旅行规划智能体的系统提示词我反复改了十几版最后稳定下来的结构包含四个部分。角色定义要具体到能力边界。不要写你是一个旅行助手而要写你是一个旅行规划智能体能够查询实时车次信息、根据用户时间和偏好编排行程、在信息不足时主动追问。这样模型知道自己能做什么、不能做什么。工具说明要讲清楚每个工具什么时候用。比如查票工具要说明当用户提到具体出发地、目的地和日期时调用如果用户只说了目的地没说出发地先追问出发地。很多智能体跑偏就是因为模型不知道工具的触发条件该调的时候不调不该调的时候乱调。输出格式约束决定了最终结果能不能直接用。我要求模型输出结构化的行程方案包含日期、时间段、活动内容、交通方式、备注五个字段用 Markdown 表格呈现。这样用户拿到就能直接看不需要再整理。异常处理规则是最容易被忽略的部分。要明确告诉模型如果工具返回空结果怎么办、如果用户需求矛盾怎么办、如果查询超时怎么办。比如如果查询结果显示无票不要编造替代方案如实告知并建议调整日期或车次。2.2 意图识别与槽位填充的实战技巧用户说我想下个月找个周末去周边玩两天这句话里包含的信息其实很不完整。出发地没说、具体哪个周末没说、周边是多远没说、预算没说、同行几人没说。智能体要做的第一件事是识别意图这是旅行规划请求第二件事是填充槽位把缺失的关键信息补全。我的做法是在系统提示词里定义一个必填槽位清单出发地、目的地、出行日期、出行人数。当用户输入后模型先检查这些槽位是否齐全缺哪个就问哪个。这里有个技巧不要一次把所有问题都抛出来那样用户体验很差。按重要性排序先问最关键的用户回答后再问下一个。实测下来把槽位检查逻辑写进系统提示词比在代码里做规则匹配要灵活得多。因为用户可能用各种奇怪的方式表达同一个信息比如我从杭州走和出发城市是杭州和人在杭州规则匹配要写很多分支而模型能自然理解这些变体。2.3 工具调用提示词的编写要点工具调用的提示词要解决三个问题什么时候调、传什么参数、拿到结果怎么用。什么时候调前面说了要写触发条件。传什么参数要明确每个参数的类型和格式。比如日期参数要统一成 YYYY-MM-DD 格式不能让模型一会儿传下周六一会儿传2025-06-15。拿到结果怎么用要说明结果的解读方式比如查询结果中的车次号、发车时间、到达时间、历时、二等座余票字段需要展示给用户。这里有个坑我踩过模型有时候会把工具返回的原始 JSON 直接贴给用户。所以提示词里要明确不要直接输出工具的原始返回内容要用自然语言重新组织后呈现。这个约束加上之后输出质量明显提升。3. FastAPI 后端实时票务查询接口的实现3.1 项目目录结构设计FastAPI 项目最容易犯的错是把所有代码堆在一个文件里。我建议从一开始就按职责分层目录结构大概是这样travel-agent/ ├── app/ │ ├── __init__.py │ ├── main.py # 应用入口注册路由 │ ├── config.py # 配置管理 │ ├── models/ │ │ ├── __init__.py │ │ └── schemas.py # Pydantic 请求/响应模型 │ ├── routers/ │ │ ├── __init__.py │ │ └── ticket.py # 票务查询路由 │ ├── services/ │ │ ├── __init__.py │ │ └── ticket_service.py # 票务查询业务逻辑 │ └── utils/ │ ├── __init__.py │ └── http_client.py # 统一的 HTTP 客户端封装 ├── requirements.txt └── .env这样分层的好处是路由层只管接收请求和返回响应业务逻辑在 service 层数据模型在 models 层工具函数在 utils 层。后面要加天气查询、地图距离计算直接加对应的 router 和 service 就行不会互相干扰。3.2 请求响应模型的定义用 Pydantic 定义模型是 FastAPI 的核心优势。票务查询的请求模型大概长这样from pydantic import BaseModel, Field from datetime import date class TicketQueryRequest(BaseModel): from_station: str Field(..., description出发站名称) to_station: str Field(..., description到达站名称) travel_date: date Field(..., description出行日期) train_type: str Field(defaultG, description车次类型G高铁/D动车/K普快)响应模型要考虑到查询可能失败的情况class TicketInfo(BaseModel): train_no: str depart_time: str arrive_time: str duration: str second_class_seats: str first_class_seats: str class TicketQueryResponse(BaseModel): success: bool message: str tickets: list[TicketInfo] []用Field加描述信息这些描述会出现在自动生成的 OpenAPI 文档里智能体读取文档时就能理解每个字段的含义。这个细节对工具调用的准确性影响很大。3.3 票务查询服务的核心逻辑票务查询服务要做的事是接收出发站、到达站、日期返回符合条件的车次列表。这里涉及几个关键处理。车站名称标准化。用户可能说北京也可能说北京南需要有一个映射逻辑把常用说法转成标准站名。我的做法是维护一个常用车站的别名词典查询前先做一次标准化。日期格式校验。要确保查询日期是未来日期且不超过可售日期范围。这个校验放在 Pydantic 模型里用 validator 做不合法直接返回 422 错误智能体收到错误后会提示用户修改。结果解析与清洗。原始查询结果往往包含很多冗余字段需要提取出车次号、发到时间、历时、余票这几个关键信息整理成统一的 TicketInfo 结构。余票信息要处理有票无票候补这几种状态统一成用户能理解的表述。超时与重试。外部查询接口可能不稳定要设置合理的超时时间我设的 10 秒超时后重试一次两次都失败就返回明确的错误信息让智能体知道是查询失败而不是没有车次。3.4 接口的异步处理与并发优化票务查询是典型的 IO 密集型操作用异步能显著提升并发能力。FastAPI 里把路由函数定义成async def内部用httpx.AsyncClient发异步请求。from fastapi import APIRouter import httpx router APIRouter() router.post(/query, response_modelTicketQueryResponse) async def query_tickets(request: TicketQueryRequest): async with httpx.AsyncClient(timeout10.0) as client: result await ticket_service.query(client, request) return result如果一次要查多个日期比如用户说下个周末可能指周六也可能指周日可以用asyncio.gather并发查询多个日期把总耗时从串行的 20 秒降到并行的 10 秒左右。这个优化在用户查询灵活日期时体验提升很明显。4. 智能体与后端的串联工具调用的完整实现4.1 工具定义与注册智能体要调用后端接口需要先把接口翻译成模型能理解的工具定义。以票务查询为例工具定义大概是这样tools [ { type: function, function: { name: query_tickets, description: 查询指定日期从出发站到到达站的火车票信息, parameters: { type: object, properties: { from_station: { type: string, description: 出发站名称如北京南 }, to_station: { type: string, description: 到达站名称如天津 }, travel_date: { type: string, description: 出行日期格式YYYY-MM-DD } }, required: [from_station, to_station, travel_date] } } } ]这个定义会随每次请求发给模型模型根据用户输入决定是否调用、传什么参数。description 写得越清楚模型调用越准确。4.2 对话循环的核心逻辑智能体的主循环是整个系统的心脏逻辑是这样的async def run_agent(user_input, history): history.append({role: user, content: user_input}) while True: response await call_llm(history, tools) message response.choices[0].message if message.tool_calls: history.append(message) for tool_call in message.tool_calls: result await execute_tool(tool_call) history.append({ role: tool, tool_call_id: tool_call.id, content: result }) else: history.append(message) return message.content这个循环会一直跑直到模型不再请求调用工具、直接输出文本回复为止。要注意设置最大循环次数我设的 5 次防止模型陷入无限调用。4.3 工具执行与结果回传工具执行就是根据模型给的函数名和参数去调用对应的后端接口async def execute_tool(tool_call): name tool_call.function.name args json.loads(tool_call.function.arguments) if name query_tickets: async with httpx.AsyncClient() as client: resp await client.post( http://localhost:8000/api/ticket/query, jsonargs ) return resp.text return 未知工具结果回传时要注意格式必须是字符串且要包含足够的信息让模型理解。如果查询失败返回的错误信息也要清晰比如查询超时请稍后重试而不是error。4.4 多轮对话的状态管理旅行规划往往需要多轮对话。用户先说我想去天津智能体追问从哪出发用户说北京智能体再问什么时候用户说下周六。这个过程中对话历史要完整保留模型才能理解上下文。我的做法是用一个列表维护完整的消息历史每次请求都把整个历史发给模型。这里有个成本考量历史越长token 消耗越大。所以我会在历史超过一定长度时把早期的工具调用结果压缩成摘要只保留关键信息。5. 常见问题与排查技巧实录5.1 模型不调用工具怎么办这是最常见的问题。用户明明说了查一下北京到天津的票模型却直接回复我无法查询实时票务信息。原因通常是工具描述不够清晰或者系统提示词里没有强调工具的使用场景。解决办法是在系统提示词里明确写当用户询问车次、票务、余票相关信息时必须调用 query_tickets 工具不要凭记忆回答。另外检查工具定义的 description 是否说清楚了功能模型对模糊的描述会倾向于不调用。5.2 工具参数传错怎么排查模型有时候会把日期传成下周六而不是2025-06-15或者把站名传成北京市而不是北京。排查方法是把每次工具调用的参数打日志看模型实际传了什么。解决这类问题一是在参数 description 里给明确的格式示例二是在后端做容错处理。比如日期参数后端可以尝试解析多种格式站名参数后端做别名词典匹配。双管齐下成功率会高很多。5.3 查询结果太长导致模型输出混乱一次查询可能返回几十个车次全塞给模型它可能会漏掉关键信息或者输出格式混乱。我的处理是在后端做筛选只返回最符合用户需求的若干条比如按出发时间排序取前 10 条并在返回结果里标注共查询到 N 个车次以下是前 10 个。5.4 常见问题速查表问题现象可能原因排查方向解决方法模型不调工具工具描述模糊检查 description补充使用场景说明参数格式错误缺少格式示例查看调用日志在参数描述中加示例查询超时外部接口不稳定检查网络和超时设置增加重试和超时提示输出格式混乱结果数据过多检查返回条数后端做筛选和摘要多轮对话丢失上下文历史未完整传递检查消息列表确保完整历史回传模型编造车次未强制调用工具检查系统提示词明确禁止凭记忆回答5.5 几个实操避坑心得第一工具返回结果一定要结构化。我一开始让后端返回纯文本模型解析起来经常出错。改成返回 JSON 字符串后模型理解准确率明显提升。JSON 里的字段名要用英文值可以用中文这样模型既能准确解析字段又能直接使用中文内容。第二给模型留思考的空间。在系统提示词里加一句在调用工具前先简要说明你的查询计划这样模型的推理过程会体现在输出里出问题时你能看到它是在哪一步想歪的。第三超时时间要合理。设太短正常查询也被打断设太长用户等得着急。我实测 10 秒是个比较平衡的值大部分查询能在 3 秒内返回10 秒足够覆盖网络波动。第四日志要打全。用户输入、模型输出、工具调用参数、工具返回结果这四样都要记日志。出问题时按时间顺序一看问题出在哪个环节一目了然。我用的就是简单的文件日志按天切分排查时很方便。第五别忽视错误提示的措辞。工具返回的错误信息是给模型看的模型会基于这个信息决定怎么回复用户。所以错误信息要写成模型能理解并转述的形式比如未查询到 2025-06-15 北京到天津的车次建议尝试相邻日期而不是404 Not Found。这套东西我从零搭到跑通大概花了两周其中大部分时间花在 Prompt 调优和异常处理上。真正写代码的时间反而不多因为 FastAPI 和智能体框架本身已经把很多脏活累活封装好了。核心难点在于想清楚每个环节的边界模型该做什么、代码该做什么、两者之间怎么交接。把这个想明白了实现就是水到渠成的事。后续如果要扩展我建议优先加两个能力一是天气查询因为行程安排和天气强相关二是地图距离计算用来估算景点之间的通勤时间。这两个工具加上之后智能体给出的方案就从能看变成能用了。再往后可以考虑加记忆能力让智能体记住用户的历史偏好比如喜欢靠窗座位、偏好上午出发下次规划时自动应用。这些都是一步步迭代出来的不用一开始就追求大而全。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

风光储微电网Simulink建模与仿真:架构、控制与参数配置 2026/9/28 16:48:44

风光储微电网Simulink建模与仿真:架构、控制与参数配置

搞新能源微电网仿真这事的感受,和之前只做单机控制完全不同:风电、光伏、储能三个单元摆在一个系统里,每个单元都有自己的一堆控制逻辑,连到一起后还要保证母线电压稳、功率平衡、模式切换不停电。项目标题“基于风光储互补微电网…

阅读更多 →
给Coding Agent装上决策大脑:Jev决策增强层实战指南 2026/9/28 16:48:44

给Coding Agent装上决策大脑:Jev决策增强层实战指南

1. 为什么要在 Coding Agent 里塞进一个 Jev先说清楚 Jev 是什么。Jev 是一个面向 Coding Agent 的决策增强层,你可以把它理解成给 Claude Code、Codex 这类命令行编码助手装上的一个“外置大脑”。它不替代模型本身,而是在模型和你的项目之间插一层判断…

阅读更多 →
QQ空间数据导出工具GetQzonehistory:3步完成本地备份 2026/9/28 16:48:44

QQ空间数据导出工具GetQzonehistory:3步完成本地备份

QQ空间数据导出工具GetQzonehistory:3步完成本地备份 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory GetQzonehistory 是一个 QQ 空间说说本地备份工具,解决历史…

阅读更多 →
COSCon‘25产研开源协同论坛议程解读:从科研到产业的协作之道 2026/9/28 16:48:44

COSCon‘25产研开源协同论坛议程解读:从科研到产业的协作之道

1. 为什么今年的产研开源协同论坛值得专门跑一趟等 COSCon‘25 的议程正式发布,我第一时间翻到产研开源协同论坛那一页,盯着看了很久。原因很直白:开源圈子里最不缺的是热闹,最缺的是让高校实验室、科研院所和企业研发团队真正坐到…

阅读更多 →
Substrate区块链开发框架:模块化、可组合、可验证的链构建范式 2026/9/28 16:48:37

Substrate区块链开发框架:模块化、可组合、可验证的链构建范式

1. 什么是 Substrate?它不是“基板”,而是区块链的“乐高底盘”如果你最近在技术社区、开发者群或加密项目讨论中频繁看到substrate这个词,别急着去查半导体手册——它和芯片制造里的“基板”毫无关系。这里的substrate,是 Rust 语…

阅读更多 →
Vibecoding持久化工作区:Web端管理Claude Code与Codex会话 2026/9/28 16:48:37

Vibecoding持久化工作区:Web端管理Claude Code与Codex会话

你有没有碰到过这种场景:在终端里用 Claude Code 或 Codex 写一下午代码,prompt、AI 回复、命令、报错全部挤在一个黑窗口里,切个分支、关个终端,第二天想找回昨天的上下文,发现一切归零。Vibecoding 这个词最近确实是…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉