news 2026/5/13 8:12:17

基于FastAPI与GPT构建智能天气对话API:架构设计与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于FastAPI与GPT构建智能天气对话API:架构设计与工程实践

1. 项目概述:一个基于FastAPI的智能天气对话API

最近在做一个挺有意思的Side Project,想把手头几个常用的工具和技术栈串起来,做一个能聊天的天气助手。核心想法很简单:用户用自然语言问天气,比如“明天上海会下雨吗?”,系统不仅能理解意图、调用天气API获取数据,还能像朋友聊天一样,结合上下文给出有温度、有信息的回复。最终我把它做成了一个名为skonto/vibe-coded的FastAPI应用,集成了OpenAI的GPT模型、免费的Open-Meteo天气数据,并通过一个自建的MCP服务器实现了联网搜索能力。整个过程踩了不少坑,也总结了一些在构建这类“AI+API”应用时的实用经验,今天就来详细拆解一下。

这个项目特别适合那些想快速上手AI应用开发,尤其是对结合大语言模型(LLM)与外部API(工具调用)感兴趣的开发者。你不需要是AI专家,只要会用Python和Docker,就能在半小时内把整个系统跑起来。它解决了传统天气API只是冷冰冰返回数据的问题,通过会话上下文和智能回复,让获取天气信息这件事变得自然、连贯。接下来,我会从设计思路、核心实现、避坑指南到部署细节,毫无保留地分享整个构建过程。

2. 核心架构与设计思路拆解

2.1 为什么选择FastAPI + OpenAI + MCP这个技术栈?

做这个项目前,我评估了几个方案。直接用现成的ChatGPT插件?限制太多,且无法深度定制。从头写一个复杂的NLP意图识别和对话引擎?周期太长,效果也难以保证。最终我选择了FastAPI + OpenAI GPT + 自定义MCP工具的组合,原因如下:

  1. FastAPI:作为Web框架,它的异步支持、自动生成交互式文档(Swagger UI)以及基于Pydantic的强类型数据验证,对于构建需要处理复杂JSON请求/响应的AI API来说,开发体验和效率都是顶级的。性能也足够好,能轻松应对并发请求。
  2. OpenAI GPT模型:核心的“大脑”。利用其强大的函数调用(Function Calling)能力,我们可以将“获取天气”、“搜索网络”等能力定义成标准的工具(Tools)描述,GPT模型能根据用户的问题,智能地决定是否调用、调用哪个工具,并生成符合上下文的自然语言回复。这省去了我们自己写意图分类和对话状态管理的巨大工作量。
  3. MCP(Message Control Protocol):这是我设计的一个轻量级内部协议,用于统一管理外部工具。你可以把它理解为一个“工具路由器”。所有对外的能力,如调用Open-Meteo、执行DuckDuckGo搜索,都封装成标准的MCP工具。FastAPI应用通过MCP客户端与MCP服务器通信,发起工具调用请求。这样做的好处是解耦可扩展:业务逻辑(聊天路由)不关心工具的具体实现;未来要新增工具(比如查股票、订机票),只需在MCP服务器注册即可,核心API无需改动。

整个数据流是这样的:用户消息进入FastAPI -> 携带会话历史调用OpenAI Chat Completion API -> GPT模型分析后,可能返回一个“调用get_weather工具”的指令 -> FastAPI通过MCP客户端请求MCP服务器执行 -> MCP服务器调用真实的Open-Meteo API -> 获取的天气数据返回给GPT -> GPT整合数据生成最终回复给用户。这个流程确保了AI的“思考”和“行动”是分离且可控的。

2.2 会话管理与状态保持:为什么是Redis?

一个能聊天的应用,必须能记住上下文。用户问完“北京天气如何?”,接着问“那明天呢?”,系统得知道“明天”指的是北京。我选择了Redis作为会话存储后端,主要基于以下几点考量:

  • 性能:Redis是内存数据库,读写速度极快,对于高频的会话读写(每次对话都要存取历史)至关重要。
  • 数据结构丰富:我使用Redis的Hash结构来存储每个会话(session:{session_id})。Hash的field可以灵活存储history(对话列表JSON)、preferred_city(从历史中分析出的用户偏好城市)等信息,非常贴合会话对象模型。
  • 过期策略:可以轻松为每个会话键设置TTL(生存时间),比如24小时,实现自动清理闲置会话,避免内存无限增长。
  • 与Python生态结合好:有成熟稳定的redis-py库,并且与FastAPI的依赖注入系统能很好地集成。

在实现时,我封装了一个SessionService。它不直接暴露Redis命令,而是提供create_sessionadd_message_to_historyget_session_history等高阶方法。这样,即使未来想把存储换成PostgreSQL或MongoDB,也只需要改这个服务层的实现,业务代码不受影响。这是依赖倒置原则的一个简单应用。

实操心得:会话ID的生成与传递我使用UUID4来生成会话ID,确保全局唯一。前端或客户端在首次请求POST /chat/session后,需要将这个session_id保存在本地(如浏览器的localStorage),并在后续所有POST /chat/message请求的Header或Body中带回。很多初学者会忘记在服务端校验这个ID是否存在,导致报错。我的做法是在SessionService.get_session中,如果没找到对应的Redis键,就抛出一个自定义的SessionNotFoundError,由全局异常处理器捕获并返回清晰的400错误。

3. 核心模块深度解析与实现

3.1 MCP服务器:工具调用的统一网关

MCP服务器是这个项目的“手”和“脚”,负责执行所有具体的任务。它的设计目标是标准化可插拔

3.1.1 协议设计我定义了一个非常简单的基于HTTP/JSON的协议。核心请求体如下:

{ "tool_name": "get_weather", "parameters": { "city": "London" } }

响应体则统一为:

{ "success": true, "data": { /* 工具执行结果,如天气数据 */ }, "error": null }

{ "success": false, "data": null, "error": "City not found" }

这种设计让客户端处理起来非常一致,无论调用什么工具,成功或失败的判断逻辑都是一样的。

3.1.2 工具注册与发现在MCP服务器的tools/目录下,每个工具都是一个独立的Python文件。例如tools/weather.py里定义了get_weather函数。服务器启动时,会动态扫描这个目录,将所有符合命名规范的函数收集起来,注册到一个全局的ToolRegistry字典中。这样,新增工具就是“新建一个文件,写一个函数”这么简单。

3.1.3 关键工具实现细节

  • get_weather(Open-Meteo): Open-Meteo的API确实免费且强大,但它的地理编码(通过城市名查经纬度)和天气数据是分开的两个接口。我的实现是先调用/v1/search?name={city}进行地理编码,获取第一个结果的经纬度,再用这个经纬度去调用/v1/forecast获取天气。这里有个:Open-Meteo的地理编码对中文城市名的支持有时不稳定,比如“北京市”可能返回多个结果。我的处理策略是,优先取admin1(省级)字段匹配度最高的,并在返回数据中附带完整的地区名称,让GPT在回复时能更准确地说“中国北京”,而不是模糊的“北京”。
  • web_search(DuckDuckGo): 使用duckduckgo-search这个Python库。为了避免被反爬,我设置了随机的User-Agent和请求间隔。更重要的是结果过滤:DuckDuckGo返回的摘要(snippet)质量参差不齐。我会优先选取来自权威气象网站(如weather.com、accuweather.com)或官方新闻机构的结果,并通过简单的关键词匹配(如“预报”、“天气预警”)来提升相关性,再将前3条最相关的结果返回给GPT做信息整合。

3.2 FastAPI应用层:智能路由与上下文组装

这是项目的“大脑”连接层,负责协调用户、GPT和工具。

3.2.1 聊天端点 (/chat/message) 的核心逻辑这是最复杂的端点。其内部流程如下图所示,我将其分解为几个关键步骤:

  1. 请求验证与会话加载:使用Pydantic模型验证入参,并从Redis加载指定session_id的完整对话历史。
  2. 构造GPT消息列表:这是上下文组装的关键。消息列表不能只包含用户当前的问题。它必须是一个包含“系统指令”、“历史对话”和“当前问题”的完整序列。例如:
    messages = [ {"role": "system", "content": "你是一个友好的天气助手,可以使用工具查询实时天气、预报或搜索网络信息。请用中文回复。"}, {"role": "user", "content": "上海今天热吗?"}, {"role": "assistant", "content": "上海当前气温28°C,天气晴朗,比较舒适。"}, {"role": "user", "content": "那明天需要带伞吗?"} # 当前问题 ]
    系统指令定义了AI的角色和能力。历史对话让AI拥有记忆。这个过程由MessageBuilder类专门负责。
  3. 调用OpenAI Chat Completion with Tools:这是核心的一步。在调用openai.ChatCompletion.create时,除了传入messages,还要传入tools参数。这个tools参数是一个列表,里面是所有可用工具的函数描述(遵循OpenAI的工具调用格式)。例如get_weather工具的描述会告诉GPT:“这个工具可以查询某个城市的当前天气,它需要一个city参数”。GPT会分析整个对话历史,判断是否需要调用工具。如果需要,它会在回复中提供一个特殊的tool_calls字段,指示API去调用哪个工具以及参数是什么。
  4. 处理工具调用:如果GPT的回复中包含tool_calls,我们的服务端代码不能直接执行,而是应该解析出工具名和参数,然后通过MCP客户端向MCP服务器发起请求。获取工具执行结果(真实的天气数据)后,将这个结果作为一条新的tool角色消息,追加到消息列表中,然后再次调用GPT。这次,GPT会看到工具返回的数据,并基于此生成面向用户的最终回复。
  5. 保存与返回:将用户消息、AI的最终回复(以及中间可能存在的工具调用记录)一并保存回Redis会话历史。最后将AI回复和可能用到的原始数据(如天气数据)返回给客户端。

注意事项:工具调用与流式响应上述步骤4可能涉及多次与GPT的交互(用户->GPT->工具->GPT->用户),这会导致响应时间变长。对于需要极速响应的场景,可以考虑使用OpenAI的流式响应(Streaming),先快速返回一个“正在思考”的占位符,再逐步返回最终结果。但在本项目中,为了逻辑清晰,我采用了简单的同步阻塞方式。在实际部署时,需要为这个端点设置较长的超时时间(如30秒)。

3.2.2 依赖注入与配置管理我大量使用了FastAPI的Depends。例如,数据库连接(Redis)、OpenAI客户端、MCP客户端都被做成了可注入的依赖项。这样做的好处是:

  • 易于测试:在单元测试中,可以轻松注入模拟对象(Mock)。
  • 资源管理:依赖项可以管理资源的生命周期(如启动连接、关闭连接)。
  • 代码清晰:路由函数只需要声明它需要什么,而不需要关心如何创建。

配置方面,我坚持“约定优于配置”。项目只有一个强制环境变量OPENAI_API_KEY,其他如Redis地址、服务器端口都有合理的默认值。这通过Pydantic的BaseSettings(搭配pydantic-settings库)优雅地实现,同时支持从.env文件、环境变量等多种方式加载。

4. 容器化部署与开发运维实践

4.1 Docker化部署:一键启动的奥秘

为了让项目能真正“开箱即用”,我花了很大精力优化Docker部署体验。核心是docker-compose.yml和那个交互式的start_docker.py脚本。

4.1.1 多服务编排docker-compose.yml定义了两个服务:

  • redis: 使用官方Redis镜像,数据卷挂载到本地./data/redis目录,实现数据持久化。
  • weather_chat_api: 基于项目Dockerfile构建的自定义镜像,包含了整个Python应用。它依赖redis服务,并会等待Redis健康检查通过后再启动。

4.1.2 安全的密钥管理这是很多AI项目的痛点。我坚决反对将API密钥硬编码在代码或Docker镜像里。我的方案是:

  1. 运行时交互式输入:通过start_docker.py脚本,在启动容器前,在终端中安全地提示用户输入OpenAI API密钥。这个密钥只存在于当次运行的内存中。
  2. 通过环境变量传递:脚本将用户输入的密钥通过environment字段动态地注入到docker-compose.yml的配置中,再传递给容器内的应用。
  3. 彻底的.gitignore和.dockerignore:确保任何包含密钥或敏感信息的文件(如.env.production)都不会被意外提交或打包进镜像。
# 这就是 make docker-start 背后的魔法 # 它会调用 python start_docker.py,完成交互输入和容器启动 $ make docker-start ? Enter your OpenAI API key: [您的输入会被隐藏] INFO: Starting Docker services...

4.1.3 Makefile:开发者的瑞士军刀我编写了一个功能丰富的Makefile,将常用的Docker、虚拟环境、测试命令封装成简单的make命令。比如make docker-logs -f可以跟踪查看日志,make docker-down可以清理所有容器。这极大地提升了开发效率,也降低了协作成本,新成员只需知道几个make命令就能上手。

4.2 开发环境搭建与调试技巧

对于想在本地深度开发或调试的朋友,我推荐使用虚拟环境(venv)模式。

# 一键创建虚拟环境并安装依赖 make setup-venv # 激活虚拟环境并启动开发服务器(带热重载) make dev

4.2.1 调试GPT交互这是最有趣也最具挑战的部分。我强烈建议在开发时打开详细的日志。我在代码中关键位置(如收到用户消息、发送给GPT的请求体、收到GPT的回复、调用工具前后)都加入了结构化日志。这样,当AI回复不符合预期时,你可以清晰地看到整个决策链条:

  1. GPT为什么决定调用工具(分析它的“思考”过程,可以尝试使用OpenAI的logprobs参数或查看更详细的审核日志)。
  2. 它生成的工具调用参数是否正确(比如city参数是不是你期望的“上海”而不是“Shanghai”)。
  3. 工具返回的数据是否完整、格式是否正确。

4.2.2 测试策略我为项目设计了不同层次的测试:

  • 单元测试:针对工具函数(如get_weather)、服务类(如SessionService)进行独立测试,使用Mock模拟外部API调用。
  • 集成测试:测试FastAPI端点,使用TestClient,并启动一个测试用的Redis容器。
  • 端到端测试:模拟真实用户发送一系列消息,验证整个对话流是否顺畅。由于涉及OpenAI API调用,这类测试运行较慢且消耗token,通常只在关键流程变更后运行。

5. 常见问题、性能优化与扩展思路

5.1 实战问题排查手册

在开发和部署过程中,我遇到了不少典型问题,这里整理成速查表:

问题现象可能原因排查步骤与解决方案
启动时提示OPENAI_API_KEY缺失1.start_docker.py交互输入失败。
2. 环境变量未正确传递到容器。
1. 直接运行OPENAI_API_KEY=sk-xxx docker compose up手动指定。
2. 检查docker-compose.ymlenvironment部分是否包含该变量。
聊天回复慢,超过10秒1. OpenAI API响应慢。
2. 网络延迟高。
3. GPT进行了多轮工具调用。
1. 检查OpenAI状态页。
2. 为/chat/message端点设置更长的超时(如30s)。
3. 在系统指令中限制GPT不要过度使用工具。
询问天气时,AI回复“我无法获取实时信息”1. GPT未触发工具调用。
2. MCP服务器未运行或工具调用失败。
3. Open-Meteo API服务暂时不可用。
1. 查看日志,确认GPT的回复中是否包含tool_calls
2. 检查MCP服务器进程和端口(默认5001)是否正常。
3. 手动调用curl http://localhost:5001/tool测试MCP工具。
Redis连接错误1. Redis容器未启动。
2. 网络配置导致应用容器无法访问Redis容器。
3. Redis密码或配置错误。
1.docker compose ps确认redis服务状态。
2. 在应用容器内执行ping redis测试网络连通性。
3. 检查REDIS_URL环境变量格式(应为redis://redis:6379)。
中文城市名识别不准1. Open-Meteo地理编码对中文支持有歧义。
2. 用户输入不标准(如“帝都”)。
1. 在工具调用前,尝试对城市名进行预处理(如添加国家后缀“,中国”)。
2. 在MCP工具中实现一个本地的小型城市名-经纬度映射表作为后备。

一个深度避坑技巧:处理GPT的“幻觉”调用有时,即使用户的问题与天气无关,GPT也可能“幻觉”出一个工具调用来。比如用户问“你是谁?”,GPT可能会错误地尝试调用get_weather。我的应对策略是在系统指令中明确约束:“你是一个天气助手,只有当用户明确询问与天气、气候、穿衣建议、出行计划(基于天气)相关的问题时,才使用工具。对于问候、自我介绍或其他无关问题,请直接友好地回答,不要使用工具。” 同时,在服务端代码中,也可以加入一层简单的规则过滤,如果用户消息中完全不包含天气相关关键词,可以跳过工具调用步骤,直接让GPT生成回复,这能节省token和延迟。

5.2 性能优化与扩展方向

项目跑起来后,可以考虑从以下几个方向进行优化和增强:

  1. 缓存策略:天气数据变化相对较慢。可以对Open-Meteo的请求结果进行缓存(例如用Redis,设置5-10分钟的过期时间)。当多个用户在同一短时间内查询同一城市天气时,能极大减少外部API调用,提升响应速度并尊重数据源的频率限制。
  2. 支持更多天气数据源:Open-Meteo是免费的,但有时你可能需要更细分的数据(如分钟级降水、空气质量)。可以在MCP服务器中集成和风天气、Visual Crossing等备用源,并在工具内部实现简单的故障转移和源选择逻辑。
  3. 前端界面:目前只有API。可以快速用Vue/React + Vite构建一个简单的聊天界面,通过WebSocket与后端连接,实现更流畅的对话体验。
  4. 用户偏好学习:当前只是从历史中简单提取preferred_city。可以引入更简单的机器学习模型(如统计词频),或利用GPT的少量示例学习(Few-shot Learning),在系统指令中动态插入用户的历史偏好,让AI的回复更具个性化。
  5. 部署到云平台:将Docker镜像推送到Docker Hub或GitHub Container Registry,然后利用Railway、Fly.io或甚至AWS ECS等平台进行一键部署。需要特别注意将Redis也作为云服务进行配置,并妥善管理生产环境的密钥(使用平台的Secrets管理功能)。

这个项目就像一颗种子,展示了如何将现代AI能力与传统的Web API开发无缝结合。它用到的FastAPI、Docker、Redis、以及设计模式,都是当前后端开发中的通用技能。而通过MCP对工具调用的抽象,则为AI应用接入更广阔的世界(数据库、内部系统、其他API)提供了一个清晰的范式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/13 8:10:50

NSGA-II算法核心机制剖析:从快速非支配排序到精英策略

1. NSGA-II算法为何成为多目标优化标杆 第一次接触NSGA-II算法是在2015年做无人机路径规划项目时,当时被它处理复杂约束条件的能力惊艳到了。这个由Deb教授团队改进的算法,如今已成为多目标优化领域的黄金标准。与单目标优化不同,多目标问题就…

作者头像 李华
网站建设 2026/5/13 8:10:40

3D高斯泼溅技术在机器人视觉控制中的应用与优化

1. 3D高斯泼溅技术解析:从原理到机器人视觉应用在机器人视觉控制领域,3D高斯泼溅(3D Gaussian Splatting,简称3DGS)正逐渐成为一项革命性技术。这项技术最初由计算机图形学研究者开发,用于实现实时的高质量…

作者头像 李华
网站建设 2026/5/13 8:09:37

designmodel-常见操作按钮-有许多不完善的地方,如撤销功能不健全,有些不能撤销,有些只能删除。还有些功能bug,需要重启才能修复,大家有遇到吗?

designmodel-常见操作按钮-有许多不完善的地方,如撤销功能不健全,有些不能撤销,有些只能删除。还有些功能bug,需要重启才能修复,大家有遇到吗? designmodel中挤出的设置,选择cut material可以切割。 designmodel每次创建的新平面需要点击生成后才可以确认生成。 designm…

作者头像 李华
网站建设 2026/5/13 8:09:37

DS4Windows终极指南:在Windows上完美使用PS4手柄的完整教程

DS4Windows终极指南:在Windows上完美使用PS4手柄的完整教程 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows DS4Windows是一款功能强大的开源工具,让PS4手柄在Windo…

作者头像 李华
网站建设 2026/5/13 8:04:06

Java安装完全指南:从零搭建Java开发环境

一篇搞定JDK安装、环境配置与常见问题解决 Java作为全球最流行的编程语言之一,广泛应用于企业级开发、Android应用、大数据处理等领域。无论你是编程新手还是希望更新开发环境的老手,本文将手把手带你完成Java开发环境的搭建。 一、了解Java核心组件 在…

作者头像 李华