背景与目的
当前主流 LLM 应用开发框架越来越多,如 LangChain、Dify、LlamaIndex、Semantic Kernel、Coze 等,效果更是每过几天就会给人一个大惊喜!
这些主流框架,复杂度也越来越高,刚入门的同学很多云里雾里,无从下手,本🤖 miniWeatherAgent 极简项目 基于最原生的 Python,不使用任何开发框架,以天气 Agent 的极简项目,为大家展现出 Agent 开发框架的核心逻辑和设计理念,重点突出 Agent 的核心决策与调度逻辑,总代码仅 100 余行,一眼看懂 Agent 工作流程,目的在于让大家理解 LLM 应用开发框架的流程!
🤖 miniWeatherAgent 项目介绍
本项目为极简版,但内容完整:
- 极致精简:单文件、100 余行核心代码,无冗余、无嵌套、一眼看懂;
- 聚焦核心:移除所有非必要代码,重点突出 Agent 类的框架 + 大模型 3 大决策能力 + 工具注册与调用;
- 交互简单:循环命令行,输入即查询,输入
exit即退出,无需复杂 UI; - 保留思想:完全保留微软 Agent 的主体化、工具插件化、大模型驱动决策核心思想,是最轻量化的 Agent 实践。
🤖 miniWeatherAgent 项目流程
本项目 100 余行核心代码,完全保留了 Agent 项目框架的核心设计思想,Agent 的核心调度流程一步未少,完整的大模型驱动决策 + 工具调用,其流程图如下:
主要模块说明:
- 人机交互模块:1/10 步骤,循环命令行交互,接受用户问题,启动 Agent 流程。
- weather\_agent 核心类:2-9 步骤,Agent 核心框架,完全 mini 式的保留主流 LLM 应用开发框架 原逻辑,核心流程。
- 大模型远端服务:本项目采用本地部署 LM Studio,模型为 llama-3-8b(可以换成更大效果更好的模型)。具体的部署指导参见:幻 x2025 笔记本 LM Studio 部署指导书
- tool 工具:本项目使用的工具集,weather\_tool,天气工具采用 中国气象局(CMA)官网(weather.cma.cn)提供的非公开文档化天气查询接口("https://weather.cma.cn/api/weather/view")。具体参考文档后面详细介绍。
项目的核心是 Agent 作为中间层,连接用户、本地大模型、天气工具,全程由本地 llama-3-8b 大模型驱动决策,无需人工干预,整体执行流程为 7 步闭环,流程图如下:
用户在命令行 UI 输入问题 →
- UI 将问题传递给 Agent 核心层 →
- 大模型判断是否需要调用天气工具 →
├─ 无需调用 → 3. 大模型直接生成拒绝回答(非天气问题)→ 6. UI 显示回答
└─ 需要调用 → 3. 大模型从问题中提取纯中文城市名 → 4. Agent 调用天气工具查询对应城市天气 → 5. 大模型整合天气结果生成自然语言回答 → 6. UI 显示格式化回答 → 7. 对话历史保存,支持多轮查询
🤖 miniWeatherAgent 项目结构
本项目是 Python 原生的 AI Agent 天气查询 mini 系统,核心基于 Agent 设计思想(Agent 主体 + 工具注册 + 大模型驱动决策)搭建,无复杂框架依赖,全程基于本地环境运行,整体分为 4 个核心模块,模块间解耦职责单一,适合 AI Agent 入门学习。
🤖miniWeatherAgent 项目为单文件代码,框架结构如下:
miniWeatherAgent项目
├─ 大模型: 本地部署LM Studio/大模型/API配置
├─ 工具层:weather_tool.天气查询工具,Agent的能力插件,提供天气查询核心功能
├─ Agent核心层:WeatherAgent,实现Agent主体逻辑(大模型决策+工具调用+结果生成)
└─ 交互层:循环命令行交互,提供用户与Agent的对话入口这个单文件极简项目的技术栈遵循轻量核心、无冗余依赖,仅采用 Python 生态中最基础、最核心的库 / 工具实现 AI Agent 的完整流程,无复杂框架、无复杂的第三方服务依赖,所有技术栈围绕「本地大模型调用 + 天气 API 请求 + 命令行交互」三大核心需求搭建,整体技术栈分为核心开发语言、核心第三方依赖库、外部服务 / 工具三类,且每一个技术组件都承担明确的核心职责,无多余组件。
所用技术栈:
代码完整结构如下:
简要核心流程0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣🔟(参见前面流程图)
import requests
from openai import OpenAI
0️⃣
# LM Studio
# 天气API
# 天气工具
def get_weather(city):
"""极简天气查询工具:入参纯中文城市名,轻量判键避免崩溃"""
# Agent核心框架
client = OpenAI(base_url=LM_BASE_URL, api_key=LM_API_KEY)
class WeatherAgent:
# Agent主体信息 + 工具注册(插件化核心思想)
def __init__(self):
self.tools = {"get_weather": (get_weather, "查询中国城市今日实时天气,用户问天气时必须调用")}
....
# 大模型决策1:判断是否需要调用天气工具,仅返回True/False
async def judge_tool_call(self, question):3️⃣
return ....
# 大模型决策2:从问题中提取纯中文城市名,无多余字符
async def extract_city(self, question):5️⃣
return ....
# 大模型决策3:整合上下文生成符合Agent角色的自然语言回答
async def generate_answer(self, question, context)
prompt = f"你是..."7️⃣
res = client.chat.completions.create(...8️⃣
return ....
# Agent核心调度流程(核心!完整体现大模型驱动+工具调用)
async def chat(self, question):
# 步骤1:大模型判断是否需要调用天气工具
need_call = await self.judge_tool_call(question)2️⃣
if need_call:
# 步骤2:大模型从问题中提取城市名参数
city = await self.extract_city(question)4️⃣
# 步骤3:调用注册的天气工具,获取结构化天气数据
weather_info = self.tools["get_weather"][0](city)6️⃣
else:
# 非天气问题,构造拒绝回答的上下文
# 步骤4:大模型整合上下文,生成最终自然语言回答
return await self.generate_answer(question, context)9️⃣
# 循环命令行交互
async def main():
agent = WeatherAgent()
print("===== 极简本地天气AI Agent(命令行版) =====")
print("使用说明:输入天气问题查询(如:今天上海的天气如何?),输入exit退出程序\n")
while True:
user_question = input("你:")
# 输入exit/EXIT退出循环
if user_question.strip().lower() == "exit":
print("Agent:再见!")
# 调用Agent核心方法,获取回答并打印
agent_answer = await agent.chat(user_question)1️⃣
print(f"Agent:{agent_answer}")🔟
# 程序入口
if __name__ == "__main__":
import asyncio
asyncio.run(main())🤖 miniWeatherAgent 代码详解
以下分别按照 miniWeatherAgent 项目的主体结构 ,分别详细代码介绍:
miniWeatherAgent 项目 ├─ 工具层:weather\_tool.天气查询工具,Agent 的能力插件,提供天气查询核心功能 ├─ Agent 核心层:WeatherAgent,实现 Agent 主体逻辑(大模型决策 + 工具调用 + 结果生成) ├─ 交互层:循环命令行交互,提供用户与 Agent 的对话入口 └─ 大模型: 本地部署 LM Studio/大模型/API 配置
项目依赖&配置
import requests # 调用天气API的HTTP协议模块
from openai import OpenAI # 调用 LM Studio模块
# ====================== 1. 项目依赖&配置 ======================
LM_BASE_URL = "http://127.0.0.1:1234/v1" # LM Studio本地地址
LM_API_KEY = "sk123456" # 占位符,无实际作用
LM_MODEL = "llama-3-8b" # 本地大模型名,与LM Studio一致
TEMPERATURE = 0.1 # 大模型温度,0.1结果更确定
MAX_TOKENS = 1024 # 大模型最大生成令牌数
BAIDU_WEATHER_URL = "https://weather.cma.cn/api/weather/view" # 天气API本项目只涉及 2 个第三方依赖库,如下:
工具层:weather_tool.天气查询工具
# 2. 天气工具 ======================================================================
def get_weather(city):
# 极简天气查询工具:入参纯中文城市名,轻量判键避免崩溃
# 清洗城市名,去除空格/冗余字符,避免用户输入"上海市"/"上 海"导致查询失败
city = city.strip().replace("市", "").replace("省", "").replace("自治区", "")
# 构造请求
params = {"stationid": "", "cityname": city, "province": ""}
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0"}
# 调用API并解析(无复杂异常处理,仅基础取值)
response = requests.get(BAIDU_WEATHER_URL, params=params, headers=headers, verify=False)
data = response.json()
# 轻量判键,避免KeyError崩溃(仅保证程序运行,不做复杂异常处理)
wd = data.get("data", {})
real = wd.get("realtime", {})
fore = wd.get("forecast", [{}])[0]
# 格式化返回(无数据则显示"未知")
return f"""【{city}今日天气】
实时温度:{real.get('temp', '未知')}℃
天气状况:{real.get('weather', '未知')}
风力风向:{real.get('windDir', '未知')}{real.get('windPower', '未知')}级
相对湿度:{real.get('humidity', '未知')}%
今日气温:{fore.get('tempMin', '未知')}℃ ~ {fore.get('tempMax', '未知')}℃
"""本段代码逻辑思路:清洗入参 city -> 构造调用 API 请求 -> 调用 API -> 解析 -> 格式化返回
其中最关键的在 data = response.json() 返回的 Json 结构体,在返回格式化时需要 做好匹配,json 格式如下:
{"success": true, // 接口调用状态(true=成功,false=失败) "data": { "city": { // 城市基础信息 "cityName": "上海", "provinceName": "上海" }, "realtime": { // 实时天气数据(核心,项目中解析的 real 字段) "temp": "22", // 实时温度(℃) "weather": "多云", // 实时天气状况 "windDir": "东风", // 风向 "windPower": "3", // 风力(级) "humidity": "65" // 相对湿度(%) }, "forecast": [ // 预报数据(数组,第一个元素为今日预报) { "tempMin": "18", // 今日最低温(℃) "tempMax": "25", // 今日最高温(℃) "weatherDay": "多云", // 日间天气 "weatherNight": "阴" // 夜间天气 } ] }, "msg": "success" // 状态描述(失败时返回错误信息) }
Agent 核心层:WeatherAgent
Agent 主体信息 + 工具注册
class WeatherAgent:
def __init__(self):
# Agent主体信息 + 工具注册(插件化核心思想)
self.name = "WeatherAgent" # Agent名称
self.role = "专业的天气查询助手,仅回答中国城市天气,非天气问题直接拒绝" # Agent角色
# 工具注册(核心:Agent的能力插件,字典格式:{工具名: (工具函数, 工具描述)})
# 后续新增工具时,仅需在此注册,无需修改核心逻辑,解耦工具与Agent
self.tools = {
"get_weather": (
get_weather,
"查询中国城市今日实时天气,用户问天气时必须调用"
)
}本段代码逻辑思路:定义 Agent 主体信息(name / role)-> 工具注册(tools)
关键点:工具注册
Agent 的能力插件,字典格式:
{工具名: (工具函数, 工具描述)})# 后续新增工具时,仅需在此注册,无需修改核心逻辑,解耦工具与 Agent
self.tools = {
"get_weather": (
get_weather, # 天气查询工具函数,详见 工具层:weather_tool.天气查询工具
"用于查询中国任意城市的今日实时天气和气温预报,用户问天气时必须调用" # 工具描述(给大模型的提示)
)
}大模型决策 1:判断是否需要调用天气工具
class WeatherAgent:
...
async def judge_tool_call(self, question):
# 大模型决策1:判断是否需要调用天气工具,仅返回True/False
prompt = f"判断用户问题是否需要查询天气,仅返回True或False,无任何其他内容。用户问题:{question}"
res = client.chat.completions.create(
model=LM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=TEMPERATURE,
max_tokens=10
)
return "TRUE" in res.choices[0].message.content.strip().upper()本段代码逻辑思路:构建 prompt -> 调用 LM Studio 请求 -> 返回结果
本段代码的核心,把用户输入作为上下文,构造 prompt,询问大模型,由本地大模型自主判断,是否需要调用天气工具,仅返回 True/False
构建 prompt:
构造大模型提示词(Prompt):简洁明确,让大模型仅返回 True/False,避免冗余
prompt = f"判断用户问题是否需要查询天气,仅返回 True 或 False,无任何其他内容。用户问题:{question}"
调用 LM Studio 请求:
调用本地大模型(OpenAI 兼容接口,chat.completions.create 为固定方法)
# 调用本地大模型(OpenAI兼容接口,chat.completions.create为固定方法)
response = client.chat.completions.create(
model=LM_MODEL, # 本地模型名,与LM Studio一致
messages=[{"role": "user", "content": prompt}], # 消息格式:用户角色+提示词
temperature=TEMPER, # 温度值,0.1保证结果确定
max_tokens=10, # 仅需返回True/False,设置最小令牌数,提高速度
)返回结果
判断结果:包含"TRUE"则返回 True,否则返回 False,清洗大模型返回结果(去除空格/大小写,避免大模型输出"true"/" True "等格式)
return "TRUE" in res.choices[0].message.content.strip().upper()等同于:
# 清洗大模型返回结果(去除空格/大小写,避免大模型输出"true"/" True "等格式)
res = response.choices[0].message.content.strip().upper()
# 判断结果:包含"TRUE"则返回True,否则返回False
return "TRUE" in res大模型决策 2:从问题中提取纯中文城市名
class WeatherAgent:
...
async def extract_city(self, question):
"""大模型决策2:从问题中提取纯中文城市名,无多余字符"""
prompt = f"提取用户问题中需要查询天气的城市名,仅返回纯中文,无任何其他字符。用户问题:{question}"
res = client.chat.completions.create(
model=LM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=TEMPERATURE,
max_tokens=20
)
return res.choices[0].message.content.strip()本段代码逻辑思路:构建 prompt -> 调用 LM Studio 请求 -> 返回结果
本段代码的核心,把用户输入作为上下文,构造 prompt,询问大模型,由本地大模型自主判断,提取用户问题中的城市名称。
构建 prompt
构造大模型提示词:明确要求,让大模型仅返回城市名,避免输出多余内容
prompt = f"""请提取用户问题中要查天气的城市名,仅返回纯中文,无其他字,无则返回未知。 用户问题:{question}"""
调用 LM Studio 请求:
调用本地大模型(OpenAI 兼容接口,chat.completions.create 为固定方法)
# 调用本地大模型(固定格式,与判断工具调用一致)
response = client.chat.completions.create(
model=LM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=TEMPER,
max_tokens=20, # 城市名最多2-3个字,设置最小令牌数
)返回结果
清洗结果:去除空格,返回纯中文城市名
return response.choices[0].message.content.strip()
大模型决策 3:整合上下文生成符合 Agent 角色的自然语言回答
class WeatherAgent:
...
async def generate_answer(self, question, context):
"""大模型决策3:整合上下文生成符合Agent角色的自然语言回答"""
prompt = f"你是{self.name},你的角色是{self.role},请根据给定的上下文简洁回答用户问题。用户问题:{question},上下文:{context}"
res = client.chat.completions.create(
model=LM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=TEMPERATURE,
max_tokens=MAX_TOKENS
)
return res.choices[0].message.content.strip()本段代码逻辑思路:构建 prompt -> 调用 LM Studio 请求 -> 返回结果
本段代码的核心,把用户输入作为上下文,构造 prompt,询问大模型,由本地大模型自主判断,优化和简洁用户问题的答案。
构建 prompt
构造大模型提示词:明确要求,由本地大模型整合上下文(天气结果/拒绝理由),生成友好的自然语言回答。
prompt = f"你是{self.name},你的角色是{self.role},请根据给定的上下文简洁回答用户问题。用户问题:{question},上下文:{context}"
调用 LM Studio 请求:
res = client.chat.completions.create(
model=LM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=TEMPERATURE,
max_tokens=MAX_TOKENS
)返回结果
清洗结果:去除空格,返回自然语言回答
return res.choices[0].message.content.strip()
Agent 核心调度流程
class WeatherAgent:
...
async def chat(self, question):
"""Agent核心调度流程(核心!完整体现大模型驱动+工具调用)"""
# 步骤1:大模型判断是否需要调用天气工具
need_call = await self.judge_tool_call(question)
if need_call:
# 步骤2:大模型从问题中提取城市名参数
city = await self.extract_city(question)
# 步骤3:调用注册的天气工具,获取结构化天气数据
weather_info = self.tools["get_weather"][0](city)
context = weather_info
else:
# 非天气问题,构造拒绝回答的上下文
context = "该问题不是天气查询问题,我仅能回答中国城市的今日天气查询,请输入天气相关问题"
# 步骤4:大模型整合上下文,生成最终自然语言回答
return await self.generate_answer(question, context)本段代码逻辑思路:基于大模型思考用户问题是否是问天气? -> 若是,让大模型提取城市 -> 调用天气工具 -> 基于工具返回的结果 -> 调用大模型润色出用户语言
本段代码的核心,是项目的核心大脑,整合 3 大决策能力(是否调用工具、提取参数、生成回答)+ 工具调用,实现 Agent 的核心逻辑,连接用户、本地大模型、天气工具,全程由本地 llama-3-8b 大模型驱动决策,无需人工干预,整体执行流程自闭环。
交互层:循环命令行交互
# ====================== 1/10. 循环命令行交互(极简交互) ======================
async def main():
agent = WeatherAgent()
print("===== 🤖 miniWeatherAgent 极简本地天气AI Agent(命令行版) =====")
print("使用说明:输入天气问题查询(如:今天上海的天气如何?),输入exit退出程序\n")
while True:
user_question = input("问题:")
# 输入exit/EXIT退出循环
if user_question.strip().lower() == "exit":
print("Agent:再见!")
break
# 调用Agent核心方法,获取回答并打印
agent_answer = await agent.chat(user_question)
print(f"🤖 miniWeatherAgent回答:{agent_answer}")本段代码逻辑思路:UI 展示文字界面 -> while 循环处理用户问题 -> 调用 Agent 查询结果 -> 返回的结果
本段代码的核心,调用 Agent 核心方法,获取回答并打印:
agent_answer = await agent.chat(user_question)print(f"🤖 miniWeatherAgent 回答:{agent_answer}")
大模型: 本地部署 LM Studio
本项目的大模型,是一个本地独立的服务,基于 LM Studio 本地服务:
# ====================== 1. 项目依赖&配置 ======================
LM_BASE_URL = "http://127.0.0.1:1234/v1" # LM Studio本地地址
LM_API_KEY = "sk123456" # 占位符,无实际作用
LM_MODEL = "llama-3-8b" # 本地大模型名,与LM Studio一致
TEMPERATURE = 0.1 # 大模型温度,0.1结果更确定
MAX_TOKENS = 1024 # 大模型最大生成令牌数LM Studio 本地服务管理如下:
项目启动时,通过如下对象连接到 LM Studio 本地服务 llama-3-8b 大模型:
client = OpenAI(base_url=LM_BASE_URL, api_key=LM_API_KEY)
大模型在本项目中的作用:
- 大模型驱动决策:是否调用工具、提取什么参数,完全由本地 llama-3-8b 判断,体现 AI Agent 的自主决策能力;
- 结果智能整合:大模型将天气工具返回的结构化数据,转化为用户易读的自然语言,体现 AI Agent 的结果处理能力;
tool(cma/api/weather/view 接口)介绍
声明:本接口只用于教学演示,请勿用于商业目的,任何纠纷和本文档无关。
该接口是中国气象局(CMA)官网(weather.cma.cn)提供的非公开文档化天气查询接口,属于气象局官网前端页面(如 “天气视图” 模块)的 “隐藏接口”—— 未在气象局官方开发者平台(如中国气象数据网)正式发布,而是通过网页抓包、格式测试等方式从官网交互逻辑中提取
基础调用格式(GET 请求)
http 调用方式:
GET https://weather.cma.cn/api/weather/view?stationid=${stationId}&cityname=${cityname}&province=${province}- 请求方式:仅支持
GET,不支持POST等其他方式; - 协议:支持
HTTPS(推荐,避免数据传输风险),也可使用HTTP(部分旧场景)。
关键参数说明(3 个核心参数,非全部必填)
特殊规则:
- 参数优先级:
stationid>cityname(若同时传,优先按stationid查询); - 空参数兼容:若仅传
cityname,接口会自动匹配对应城市的监测站;若 3 个参数均为空,接口返回 IP 属地城市 的天气数据(如搜索摘要 1 所述)。
返回数据格式(JSON)
接口返回结构化 JSON 数据,核心包含 “实时天气”“今日预报”“城市信息” 3 类数据,简化示例如下(与你的项目 get_weather 函数解析逻辑对应):
json 块:
{"success": true, // 接口调用状态(true=成功,false=失败)
"data": {
"city": { // 城市基础信息
"cityName": "上海",
"provinceName": "上海"
},
"realtime": { // 实时天气数据(核心,项目中解析的 real 字段)
"temp": "22", // 实时温度(℃)
"weather": "多云", // 实时天气状况
"windDir": "东风", // 风向
"windPower": "3", // 风力(级)
"humidity": "65" // 相对湿度(%)
},
"forecast": [ // 预报数据(数组,第一个元素为今日预报)
{ "tempMin": "18", // 今日最低温(℃)
"tempMax": "25", // 今日最高温(℃)
"weatherDay": "多云", // 日间天气
"weatherNight": "阴" // 夜间天气
}
]
},
"msg": "success" // 状态描述(失败时返回错误信息)
}调用限制(非官方说明,基于实践总结)
- 频率限制:气象局对该接口有隐性访问频率控制(如搜索摘要 1 所述 “不推荐频繁访问,以免对服务器造成压力”),短时间内多次调用可能触发 “系统内部异常”“访问受限” 等报错;
- 地域限制:仅支持查询中国境内城市(含港澳台),不支持国外城市;
- 数据更新频率:实时天气约 10-15 分钟更新一次,预报数据约 1 小时更新一次(与气象局官网同步)。
🤖 miniWeatherAgent 运行效果
项目运行步骤
步骤 1:启动 LM Studio 本地大模型 API
打开 LM Studio→「Server」→ 选择 llama-3-8b→ 点击「Start Server」,确保控制台显示 Server running on http://127.0.0.1:1234。
步骤 2:
- 将代码保存为
simple_weather_agent.py; - 命令行进入文件所在目录,执行:
- 运行
python simple_weather_agent.py或直接在 VS2026 IDE 环境中点执行
步骤 3:运行成功后,弹出对话界面:
步骤 3:输入问题”今天上海的天气如何?“,回车,得到如下结果:
=====miniWeatherAgent 极简本地天气 AI Agent(命令行版) =====
使用说明:输入天气问题查询(如:今天上海的天气如何?),输入 exit 退出程序
问题:今天上海的天气如何?
🤖 miniWeatherAgent 回答:今天上海的天气是晴朗,温度在 15\~20 摄氏度之间。
问题:\_
6. PythonTest 项目的源码和项目功能描述
项目源代码:https://github.com/hechunji/simple\_weather\_agent
END
觉得内容不错?我要
很好
不错!