第4次作业
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/16760 |
| 这个作业的目标 | 构建能够理解自然语言、通过 MCP 调用真实天气接口并生成出行建议的智能体,理解模型、工具与网页界面的协作方式。 |
| 成员1 | 陈宇轩 3124004089 |
| 成员2 | 李家乐 3124004096 |
| 成员3 | 陈安东 3124004085 |
| 成员4 | 李梓弘 3124004100 |
基于 LangGraph 与 MCP 的天气出行智能助手
GitHub 公共仓库
仓库链接: https://github.com/Philzair/Philzair/tree/main/weather-travel-agent 。
代码提交位置为第一次团队作业创建的 GitHub 公共仓库,项目目录为 weather-travel-agent。公开代码应包含源文件、requirements.txt 和使用说明;模型密钥、Python 环境目录及本地编辑器配置不属于公开提交内容。
演示问题:杭州明天天气怎么样?适合带娃去西湖吗?
一、需求描述
1. 使用场景
出行之前,用户经常需要同时考虑天气、活动类型和同行人员。例如,“杭州明天天气怎么样?适合带娃去西湖吗?”既包含天气查询,也包含出行判断。
用户希望一次提问就能获得对应日期的天气,以及雨具、衣着、防晒和活动安排方面的建议。
本项目选择天气出行作为应用场景,提供中文聊天界面。用户输入自然语言,智能体识别城市、时间和活动需求,查询真实天气,再生成出行建议。
2. 功能需求
- 自然语言交互:接受中文输入,并用中文说明天气情况和出行建议。
- 当前天气查询:根据城市名称获取温度、湿度、风速和天气代码。
- 天气预报查询:获取从当天开始的 1~7 天天气预报,包括日期、温度、降水概率、风速和紫外线指数。
- 出行建议生成:结合天气数值和活动需求,提供户外、室内、亲子等出行建议。
- 会话上下文传递:保存当前会话中的历史消息,在后续提问时将上下文传给模型。
- 网页交互:通过 Streamlit 展示聊天记录、输入框和请求处理状态。
以亲子出行为例,回答需要同时照顾两个方面:一方面给出日期、温度和降水概率等查询结果;另一方面根据这些条件说明适合安排什么活动、需要准备什么物品。用户可以据此判断行程是否合适。
3. 作业要求对应关系
- “能说”:模型理解用户输入,生成自然语言回答。
- “会做”:模型通过 MCP 调用天气查询与出行建议工具。
- MCP 框架:使用 FastMCP 注册工具,使用
langchain-mcp-adapters连接服务。 - 外部 API 交互:使用 Open-Meteo 的地理查询和天气接口。
- 前端界面:使用 Streamlit 提供网页聊天界面。
- 多轮对话记忆:代码通过
st.session_state保存消息并传递历史上下文;完整连续两轮对话的验证状态在功能演示部分说明。
二、业务流描述
1. 页面接收问题
用户提交问题以后,app.py 将输入加入 st.session_state.messages,并在页面显示这条消息。
程序随后调用 run_agent,将当前会话中的历史消息一起传入。首次提问时,消息主要包含当前问题;后续提问时,模型还能看到前面的用户消息和助手回答。
2. Agent 选择天气工具
build_agent 读取模型配置,连接 weather 和 travel 两个 MCP 服务,获取三个工具的定义,然后创建 Agent。
对于演示问题,模型需要识别三个信息:
- 城市:杭州。
- 时间:明天。
- 活动需求:带孩子去西湖游玩。
模型根据工具说明生成调用参数,例如 get_forecast(city="杭州", days=2)。MCP 客户端将调用交给天气服务,天气服务完成城市经纬度查询和预报请求。
3. 根据天气生成建议
天气工具返回从当天开始的预报。模型选取第二个日期的数据,将温度、降水概率、风速和紫外线指数等信息传给 suggest_travel_plan。
出行建议工具根据本地规则返回建议。模型再结合原始问题,将天气和建议组织成中文回答。回答应写出具体日期,使用户能够核对查询时间。
4. 展示结果并保存上下文
页面使用 st.markdown 展示回答,并将回答加入会话历史。
如果用户接着问“那上海呢?”,下一次请求会包含上一轮内容,为模型继承日期和活动需求提供依据。
执行过程中的分工:模型负责理解问题、选择工具和组织语言;MCP 负责传递工具调用;天气服务负责查询数据;出行建议服务负责执行本地规则。LangGraph 将模型请求和工具执行连接起来,直到模型生成最终回答。实际调用次数由当前请求决定。
三、实现说明
1. 使用的技术
- Python:编写页面、Agent 和 MCP 服务。
- Streamlit:构建聊天页面,管理当前会话的消息。
- LangGraph:通过
create_react_agent构建工具调用流程。 - langchain-openai:通过
ChatOpenAI连接支持工具调用的兼容模型接口。 - FastMCP:将 Python 函数注册为 MCP 工具。
- langchain-mcp-adapters:发现 MCP 工具,并将其接入 Agent。
- httpx:执行地理查询与天气查询的异步 HTTP 请求。
- python-dotenv:读取
.env中的模型配置。
本项目使用 Python 3.12.10 完成运行检查。
2. 文件职责
app.py:网页入口、聊天记录展示、用户输入处理和 Agent 调用。agent/graph.py:读取模型配置,创建 MCP 客户端并构建 Agent。agent/prompts.py:定义工具使用流程、日期选择和上下文理解要求。servers/weather_server.py:提供当前天气和天气预报工具。servers/travel_server.py:提供出行建议工具。requirements.txt:记录需要安装的 Python 依赖。README.md:说明环境配置、启动命令与演示方法。
3. MCP 工具连接
工具如何提供给模型
_build_client 创建 MultiServerMCPClient,配置 weather 和 travel 两个服务。两个服务使用 stdio 通信,由客户端启动对应的 Python 子进程。
服务端使用 @mcp.tool() 注册函数,函数名称、参数类型和说明用于描述工具的用途。客户端获取这些定义后,将其提供给 Agent,模型据此决定调用哪个工具。
天气服务配置如下:
"weather": {
"command": "python",
"args": ["servers/weather_server.py"],
"transport": "stdio",
"cwd": os.getcwd(),
}
travel 服务使用相同方式启动 servers/travel_server.py。运行应用时,需要在项目目录启动,并让 PATH 中的 python 指向已经安装依赖的解释器。
Agent 如何使用工具
Agent 构建过程中的关键代码为:
client = _build_client()
tools = await client.get_tools()
agent = create_react_agent(
model=llm,
tools=tools,
prompt=SYSTEM_PROMPT,
)
get_tools() 获取工具名称、说明和参数定义。模型可以根据这些信息选择工具,并生成调用参数。
这里的 llm 是 ChatOpenAI 创建的模型对象,tools 是从两个 MCP 服务取得的工具,SYSTEM_PROMPT 则规定天气查询、出行建议和日期选择的要求。
当模型提出工具调用时,Agent 执行相应工具,并将结果加入本次执行的消息中。模型读取工具结果后,可以继续调用工具,也可以直接生成回答。
4. 天气查询工具
天气服务提供 get_current_weather 和 get_forecast。
城市名称如何变成天气数据
查询开始时,_geocode(city) 将城市名称转换为经纬度。当前代码从地理查询结果中读取第一个匹配城市;如果没有查询结果,会产生 ValueError。
随后,天气接口根据经纬度返回相应地区的数据。两个步骤均使用 httpx.AsyncClient,请求超时设置为 15 秒。天气请求设置 timezone="auto",由接口根据位置选择时区。
两个天气工具分别返回什么
get_current_weather 返回当前温度、相对湿度、风速和天气代码。
get_forecast 请求每日预报字段,返回日期、最低温度、最高温度、最大降水概率、最大风速和最大紫外线指数。工具返回文本中保留日期,供模型选择对应日期的数据。
如何理解“今天”和“明天”
days=1:包含当天的预报。days=2:包含当天和明天的预报。days=3:包含当天、明天和后天的预报。
因此,天数参数表示从当天开始获取多少天的数据。系统提示词要求“明天”使用第二个日期,“后天”使用第三个日期,并要求回答写出实际日期。
日期检查:工具返回的日期、出行建议采用的数据日期和最终回答中的日期,需要保持一致。当前程序通过提示词要求模型选择日期,没有另外实现选择结果的自动校验。
5. 出行建议工具
suggest_travel_plan 接收城市、天气摘要、温度范围、降水概率、最大风速、紫外线指数和 preference 参数。其中,preference 用于传递“户外”“室内”“带娃”等活动需求。
天气条件与建议
- 降水概率达到 60% 时,建议携带雨具并优先考虑室内活动。
- 降水概率达到 30% 且低于 60% 时,提醒携带折叠伞,预留室内活动选择。
- 最高温度达到 32°C 时,提醒避开中午暴晒并及时补水。
- 最低温度不高于 5°C 时,提醒携带外套。
- 最大风速达到 25 km/h 时,提醒谨慎选择骑行、高空或水上活动。
- 紫外线指数达到 7 时,提醒使用防晒用品。
- 根据
preference和天气条件,补充户外、室内或亲子活动建议。
这些条件分别检查,因此同一次调用可能得到多项提醒。例如,气温较高且紫外线指数达到阈值时,工具会同时返回补水和防晒建议。
活动需求如何参与判断
活动需求相关的判断使用 if/elif:满足前面的条件后,就不会继续执行后面的分支。降水概率达到 60% 时,该部分会优先生成室内活动建议;天气条件允许时,“带娃”分支可以补充亲子友好场所和室内备选等提醒。
weather_summary 当前保留在参数中,规则判断主要使用温度、降水概率、风速、紫外线指数和 preference。最终回答由模型结合工具结果组织。
当前工具生成天气条件下的活动建议。景点开放时间、票务和交通路线没有接入查询服务。
6. 网页与会话管理
Streamlit 使用 st.chat_input 接收输入,使用 st.chat_message 展示消息,使用 st.spinner 展示处理状态。
每次调用 Agent 时,程序传入当前会话中的用户消息和助手回答:
[
{"role": m["role"], "content": m["content"]}
for m in st.session_state.messages
]
这样,模型可以结合上一轮内容理解“那上海呢?”等省略城市以外信息的提问。
会话保存范围:消息保存在当前 Streamlit 会话中。项目没有接入数据库保存长期对话,也没有实现跨设备恢复聊天记录。
等待过程:调用模型和天气接口需要时间。st.spinner 会在处理期间显示提示;具体请求执行到哪一步,可以结合终端中的工具与 HTTP 请求记录检查。
四、环境配置与运行
1. 安装依赖
在 vier 目录打开 Windows PowerShell,执行:
Set-Location weather-travel-agent
python -m venv .venv-local
$env:PATH = (Resolve-Path .venv-local/Scripts).Path + ';' + $env:PATH
python -m pip install -r requirements.txt
python -m pip check
每次使用新的终端窗口时,需要在项目目录重新执行设置 PATH 的命令,使应用和 MCP 子进程使用同一个 Python 环境。
可以通过以下命令确认当前解释器:
python -c "import sys; print(sys.executable)"
输出应指向本机创建的 .venv-local\Scripts\python.exe。python -m pip check 用于检查已安装依赖之间是否存在版本要求冲突。
2. 配置模型接口
在项目目录创建 .env 文件,填写实际模型配置:
LLM_API_KEY=YOUR_API_KEY
LLM_BASE_URL=YOUR_API_BASE_URL
LLM_MODEL=YOUR_MODEL_NAME
三个值分别对应模型服务密钥、接口地址和模型名称。选择的模型需要支持工具调用。配置文件中的密钥需要保存在本地。
.env 与 app.py 放在同一目录。程序通过 load_dotenv() 读取配置,ChatOpenAI 使用这些配置向模型服务发送请求。修改配置后,需要重新启动应用。
3. 启动网页
python -m streamlit run app.py
根据终端提示打开本地页面,输入问题即可提交请求。应用会自动启动 MCP 工具服务。
第一次演示可以使用“杭州未来三天天气怎么样?”这样的明确问题,检查能否返回带日期的预报;随后再检查带活动需求的问题。结束运行时,在终端按 Ctrl+C。
五、使用示例与功能演示
1. 当前天气与预报工具
已有运行记录中,MCP 客户端成功发现以下工具:
get_current_weather
get_forecast
suggest_travel_plan
2026 年 10 月 21 日的一次真实查询中,get_forecast(city="杭州", days=3) 返回了以下数据:
杭州从今天开始未来 3 天预报:
2026-10-21:14.7~20.3°C,降水概率 100%,最大风速 17.4 km/h,紫外线指数 4.6
2026-10-22:13.3~21.4°C,降水概率 0%,最大风速 16.7 km/h,紫外线指数 5.9
2026-10-23:16.1~22.2°C,降水概率 82%,最大风速 14.3 km/h,紫外线指数 5.3
这些数值属于当时获取的预报,后续查询结果会随天气服务更新。
2. 单轮天气与亲子出行建议
测试时输入:
杭州明天天气怎么样?适合带娃去西湖吗?
在 2026 年 10 月 21 日的一次应用测试中,Agent 请求了两天天气预报,调用出行建议工具,并返回了对应 2026 年 10 月 22 日的回答。回答中的天气信息包括:
- 温度:13.3~21.4°C。
- 降水概率:0%。
- 最大风速:16.7 km/h。
- 紫外线指数:5.9。
回答同时给出了亲子户外活动、防晒和室内活动选择等建议。天气查询和本地建议工具均参与了这次请求。
如何核对这次演示:
- 查看天气请求中的
forecast_days,确认请求包含明天的数据。 - 查看天气工具返回文本,找到目标日期对应的温度和降水概率。
- 将这些数值与最终回答逐项核对。
- 查看终端记录,确认调用了出行建议工具。
这几项检查能够分别确认查询范围、数据使用和建议生成过程。
3. 连续追问示例
第二轮输入可以使用:
那上海呢?
该场景用于检查模型能否继承上一轮的日期和亲子出行需求,并查询上海天气。当前代码已传递历史消息;最近一次连续对话检查中,第二轮模型请求在等待期间没有返回,测试被终止,因此该场景尚未通过完整验证。
4. 已有测试记录
- 六个 Python 源文件通过语法检查。
- 独立运行环境的
pip check检查通过。 - 两个 MCP 服务成功提供三个工具,真实工具调用成功。
- Streamlit HTTP 服务启动成功,首页与健康检查返回 HTTP 200。
- 单轮 Agent 对话成功返回天气和出行建议。
- 连续两轮对话的完整验证尚未完成。
这些记录分别检查了程序启动、工具执行和对话行为。页面能够访问与对话能够完成需要分别检查。
六、小组成员心得
1. 成员一:陈宇轩
一句提问里包含了哪些任务
刚开始接触这次作业时,我对智能体的理解还比较模糊,觉得接入模型、做一个聊天页面,应该就能完成主要功能。
看过项目里的调用过程之后,我才发现,一句“杭州明天适合出去玩吗”里面有好几个需要处理的信息:城市是杭州,时间是明天,用户还希望得到活动建议。模型需要选择合适的工具,把参数传过去,拿到结果以后继续处理。这里面任何一个步骤没有说明清楚,都可能影响最后的回答。
结合代码理解 MCP
我觉得比较有意思的是 MCP 这部分。天气查询和出行建议分别放在两个服务里,Agent 通过客户端获取工具,再根据用户的问题调用。结合代码看这个过程,比单独阅读概念容易理解。get_current_weather 和 get_forecast 的作用不同,工具说明也需要让模型看得明白。函数可以正常执行,只能说明这个函数本身能够工作;模型能否在合适的时候调用它,还需要放进完整对话里检查。
提示词还需要配合程序检查
这次项目也让我对提示词有了不同的认识。像“查询明天的天气”这样看起来很普通的要求,其实需要明确预报从哪一天开始,应该读取哪一天的数据。提示词写得具体一些,模型才有明确的依据。
不过,我也觉得不能把所有正确性都交给提示词保证。涉及日期和数值的地方,程序能够进行检查的话,使用起来会更让人放心。这也是我接下来想继续学习的部分。
2. 成员二:李家乐
城市名称后面还有查询过程
我对天气接口这部分印象比较深。平时使用天气软件,只需要输入城市就能看到结果,在代码里却要经过城市名称查询、获取经纬度、请求天气数据这些步骤。接口返回的信息也很多,程序需要选择温度、降水概率、风速和紫外线指数等字段,再交给后面的出行建议工具。这个过程让我体会到,接入 API 以后,读取哪些字段、保留哪些单位、如何使用日期,都需要认真检查。
“明天”需要对应具体日期
项目里最值得注意的一个细节是“明天”。天气接口提供的预报从当天开始,因此获取一天预报时拿到的是当天数据,获取两天才能包含明天。
用户提问时通常不会关心这些规则,他们只会看最后的回答。如果日期选择不准确,即使每个数值都来自真实接口,也可能给出不合适的出行建议。所以我觉得检查天气功能时,应该把回答中的日期和接口返回的日期放在一起核对,不能只看有没有返回温度。
建议需要有对应的数据依据
出行建议的规则也让我思考了不少。降水概率达到多少需要提醒带伞,风速达到多少需要提醒谨慎选择活动,这些规则写在程序中比较直观,也方便检查。不过,真实出行还会受到景点开放时间、交通和同行人员等因素影响。当前项目能够提供天气相关的建议,阅读结果时仍然需要知道这些建议覆盖了哪些信息。以后接入更多服务时,我会更加注意数据来源和使用范围。
3. 成员三:陈安东
输入之后,页面要有反馈
我比较关注用户打开网页以后会怎样使用这个程序。聊天界面本身很简洁,一个输入框加上历史消息,就能够完成基本交互。但从用户的角度看,输入问题之后有没有反馈、等待的时候能不能知道程序正在处理、回答里能不能快速找到日期和天气,这些都会影响使用感受。Streamlit 提供了现成的聊天组件,让我可以把注意力放在这些具体问题上。
追问依赖前面的消息
会话记录也是一个容易被忽略的地方。用户第一轮问杭州,第二轮只说“那上海呢”,一般会默认程序还记得前面问的是哪一天、什么活动。代码需要把历史消息继续传给模型,才能给它理解这句话的依据。
st.session_state 可以保存当前会话的消息,但保存消息和完整完成第二轮回答之间还有其他环节,例如模型请求和工具调用。项目中的连续对话还没有完成全部验证,这让我对“支持多轮对话”这个说法更加谨慎,展示时需要说明实际检查到了哪一步。
回答要方便用户找到重点
另外,我觉得页面上的回答需要注意阅读顺序。日期、温度和降水概率应该让用户容易找到,出行建议也应该围绕用户问的问题展开。用户询问适不适合带孩子出去,通常希望尽快知道天气条件和需要准备的物品。以后继续完善界面时,我希望让这些信息更清楚,也希望请求长时间没有返回时,页面能够提供明确的处理状态。
4. 成员四:李梓弘
换一台电脑也需要能够运行
我在这个项目中比较在意的是,程序换一台电脑以后还能不能按照说明运行。以前看到项目里有运行环境目录,会觉得直接打开就能使用。这次项目附带的 .venv 引用了其他电脑上的 Python 路径,当前电脑需要重新创建环境。这个细节让我意识到,依赖清单和启动说明很重要,环境目录本身并不能保证别人顺利运行程序。
启动命令需要写清楚运行条件
MCP 子进程使用哪个 Python 也需要注意。页面使用的解释器安装了依赖,如果子进程启动时找到另一个 python,仍然可能出现模块无法导入的问题。还有启动目录,代码通过相对路径寻找服务文件,就需要在包含 app.py 的目录运行。
这些问题单独看都不复杂,但如果说明里缺少一步,其他成员就可能花时间检查与业务功能无关的地方。因此我觉得文档应该写清楚命令从哪个目录执行、环境怎样创建,以及模型配置放在哪里。
不同测试结果要分别记录
测试方面,我最大的感受是需要分开记录不同检查的结果。天气工具调用成功,说明工具这一部分有了运行依据;网页返回 HTTP 200,说明服务能够访问;完整对话还需要继续检查模型有没有返回、工具数据有没有正确使用。连续对话测试中出现等待较长的情况时,也需要保留当时的输入和执行记录。把这些情况写清楚,后续检查才能接着进行,小组成员之间交流问题时也会更具体。
七、学习资源推荐
1. MCP 官方快速入门与 Python SDK 文档
适合了解 MCP 中客户端、服务端与工具的关系。阅读时可以重点关注 stdio 通信、工具注册、参数定义和客户端调用,再对应项目中的 FastMCP、mcp.tool() 和 MultiServerMCPClient 理解执行过程。
2. LangGraph 官方文档
适合学习 Agent 如何在模型与工具之间传递消息,以及如何管理执行状态。可以结合项目中的 create_react_agent,观察模型发起工具调用之后,工具结果如何参与下一次模型请求。
3. Streamlit 官方文档
建议关注聊天界面、st.session_state 和应用测试。理解页面脚本的重新执行方式,有助于处理会话消息、资源创建和异步请求。
4. Open-Meteo API 文档
建议关注地理查询、每日预报、时区和返回字段。阅读接口说明时,需要核对预报的起始日期、字段单位和参数范围。
5. Hello-Agents 教程
可以作为智能体实践学习材料,结合工具调用、记忆与协作等主题继续学习。阅读教程时,可以尝试把概念对应到本项目的模型、MCP 工具、会话历史和测试流程。
浙公网安备 33010602011771号