本文虽为新手向,但仍需一定动手和查询能力,还需要一定的好奇心和探索精神才能更好地调优
Tl;DR
"""
title: Keyless Weather
author: spyci
author_url: https://github.com/open-webui
funding_url: https://github.com/open-webui
version: 0.1.1
"""
import os
import requests
import urllib.parse
import datetime
def get_city_info(city: str):
url = f"https://geocoding-api.open-meteo.com/v1/search?name={urllib.parse.quote(city)}&count=1&language=en&format=json"
response = requests.get(url)
if response.status_code == 200:
try:
data = response.json()["results"][0]
return data["latitude"], data["longitude"], data["timezone"]
except (KeyError, IndexError):
print(f"City '{city}' not found")
return None
else:
print(f"Failed to retrieve data for city '{city}': {response.status_code}")
return None
wmo_weather_codes = {
"0": "Clear sky",
"1": "Mainly clear, partly cloudy, and overcast",
"2": "Mainly clear, partly cloudy, and overcast",
"3": "Mainly clear, partly cloudy, and overcast",
"45": "Fog and depositing rime fog",
"48": "Fog and depositing rime fog",
"51": "Drizzle: Light, moderate, and dense intensity",
"53": "Drizzle: Light, moderate, and dense intensity",
"55": "Drizzle: Light, moderate, and dense intensity",
"56": "Freezing Drizzle: Light and dense intensity",
"57": "Freezing Drizzle: Light and dense intensity",
"61": "Rain: Slight, moderate and heavy intensity",
"63": "Rain: Slight, moderate and heavy intensity",
"65": "Rain: Slight, moderate and heavy intensity",
"66": "Freezing Rain: Light and heavy intensity",
"67": "Freezing Rain: Light and heavy intensity",
"71": "Snow fall: Slight, moderate, and heavy intensity",
"73": "Snow fall: Slight, moderate, and heavy intensity",
"75": "Snow fall: Slight, moderate, and heavy intensity",
"77": "Snow grains",
"80": "Rain showers: Slight, moderate, and violent",
"81": "Rain showers: Slight, moderate, and violent",
"82": "Rain showers: Slight, moderate, and violent",
"85": "Snow showers slight and heavy",
"86": "Snow showers slight and heavy",
"95": "Thunderstorm: Slight or moderate",
"96": "Thunderstorm with slight and heavy hail",
"99": "Thunderstorm with slight and heavy hail",
}
def fetch_weather_data(base_url, params):
try:
response = requests.get(base_url, params=params)
response.raise_for_status()
data = response.json()
if "error" in data:
return f"Error fetching weather data: {data['message']}"
return data
except requests.RequestException as e:
return f"Error fetching weather data: {str(e)}"
def format_date(date_str, date_format="%Y-%m-%dT%H:%M", output_format="%I:%M %p"):
dt = datetime.datetime.strptime(date_str, date_format)
return dt.strftime(output_format)
class Tools:
def __init__(self):
self.citation = True
self.default_location = "你所在城市名拼音"
pass
def get_future_weather_week(self, city: str = None) -> str:
"""
Get the weather for the next week for a given city.
:param city: The name of the city to get the weather for. If None, uses default location.
:return: The current weather information or an error message.
"""
if not city:
city = self.default_location
city_info = get_city_info(city)
if not city_info:
return """Error fetching weather data"""
lat, lng, tmzone = city_info
print(f"Latitude: {lat}, Longitude: {lng}, Timezone: {tmzone}")
base_url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": lat,
"longitude": lng,
"models": "ecmwf_ifs025",
"daily": [
"weather_code",
"sunrise",
"sunset",
"temperature_2m_max",
"temperature_2m_min",
"cloud_cover_mean",
"relative_humidity_2m_mean",
"precipitation_probability_max",
"wind_speed_10m_max",
"wind_gusts_10m_max",
"wind_direction_10m_dominant",
],
"current": "temperature_2m",
"timezone": tmzone,
"forecast_days": 7,
}
data = fetch_weather_data(base_url, params)
if isinstance(data, str):
return data
formatted_timestamp = format_date(data["current"]["time"])
data["daily"]["time"][0] += " (Today)"
mapped_data = {
date: {
"weather_description": wmo_weather_codes[
str(data["daily"]["weather_code"][i])
],
"sunrise_sunset": f'Sunrise: {format_date(data["daily"]["sunrise"][i])} / Sunset: {format_date(data["daily"]["sunset"][i])}',
"temperature_max_min": f'{data["daily"]["temperature_2m_max"][i]} {data["daily_units"]["temperature_2m_max"]} / {data["daily"]["temperature_2m_min"][i]} {data["daily_units"]["temperature_2m_min"]}',
"cloud_cover": f'{data["daily"]["cloud_cover_mean"][i]} {data["daily_units"]["cloud_cover_mean"]}',
"humidity": f'{data["daily"]["relative_humidity_2m_mean"][i]} {data["daily_units"]["relative_humidity_2m_mean"]}',
"precipitation_probability": f'{data["daily"]["precipitation_probability_max"][i]} {data["daily_units"]["precipitation_probability_max"]}',
"wind": f'Speed: {data["daily"]["wind_speed_10m_max"][i]} {data["daily_units"]["wind_speed_10m_max"]} / Gusts: {data["daily"]["wind_gusts_10m_max"][i]} {data["daily_units"]["wind_gusts_10m_max"]} / Direction: {data["daily"]["wind_direction_10m_dominant"][i]} {data["daily_units"]["wind_direction_10m_dominant"]}',
}
for i, date in enumerate(data["daily"]["time"])
}
return f"""
Give a weather description for the next week, include the time of the data ({formatted_timestamp} {data['timezone_abbreviation']} in {city}):
Show a standard table layout of each of these days: {mapped_data}
Include a one sentence summary of the week at the end."""
def get_current_weather(self, city: str = None) -> str:
"""
Get the current weather for a given city.
:param city: The name of the city to get the weather for. If None, uses default location.
:return: The current weather information or an error message.
"""
if not city:
city = self.default_location
city_info = get_city_info(city)
if not city_info:
return """Error fetching weather data"""
lat, lng, tmzone = city_info
print(f"Latitude: {lat}, Longitude: {lng}, Timezone: {tmzone}")
base_url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": lat,
"longitude": lng,
"models": "ecmwf_ifs025",
"current": [
"temperature_2m",
"apparent_temperature",
"relative_humidity_2m",
"cloud_cover",
"surface_pressure",
"wind_speed_10m",
"weather_code",
],
"timezone": tmzone,
"forecast_days": 1,
}
data = fetch_weather_data(base_url, params)
if isinstance(data, str):
return data
formatted_timestamp = format_date(data["current"]["time"])
data["current"]["weather_code"] = wmo_weather_codes[
str(data["current"]["weather_code"])
]
formatted_data = ", ".join(
[
f"{x} ({data['current_units'][x]}) = '{data['current'][x]}'"
for x in data["current"].keys()
]
).replace("weather_code", "weather_description")
return f"""
Give a weather description, include the time of the data ({formatted_timestamp} {data['timezone_abbreviation']} in {city}):
Include this data: [{formatted_data}]
Ensure you mention the real temperature and the "feels like"(apparent_temperature) temperature. Convert all numbers to integers.
Keep response as brief as possible."""
- 直接替换天气工具 整个源代码。
- 大家自行修改第83行的城市名称,用汉语拼音。
- 此方法适合主要城市(重名概率低)。3,4县城市的拼音重名概率高,需再研究研究。
- 此段代码只包含我日常喜欢得到的天气数据,其他更多数据需要佬们自行添加,见下方第二部分。
缘起
为了部署几个 API 方便自己使用,被论坛佬们拉进 OpenWebUI 坑里。不懂代码,但有了 AI 帮助,也能玩的不亦乐乎。
前言
OpenWebUI 的 工具 可以扩展很多功能,给 AI 一定赋能。(比如让AI读取时间,天气,增加记忆力,获取特定网络的讯息等等)
在官方的工具页面,我们能看到很多工具。比如这次要说的 天气工具 。安装后,对任何LLM使用这个工具,只要问 地点 + 天气 这两个关键词,都能给出适合的回答。(视大模型能力,大多数中英文都可以。英文出错概率更低。)
** 我调优代码均为 Claude3.7 创造,我再辅助微调或修饰细节
分享的调优方法:
- 第一部分,增加默认地址。适合 不经常出远门的佬 ,
- 第二部分,改变气象模型,选择数据,以 优化天气预报 。
- 天气工具地址 : https://openwebui.com/t/spyci/keyless_weather
- 气象数据源 :open-meteo.com
第一部分 - 增加默认地址
此工具 默认调用方式 是 地点 + 天气 + 具体时间。数据源是open-meteo.com ,建议到网站自行选取你需要的天气数据。英文看不懂?找沉浸式翻译。
- 比如我要问“今天上海的天气怎样?”,或“广州未来7天天气怎样”,“现在成都的天气怎么样”。
我觉得常住一个地方,每次都要问一个固定地点,显得我很傻,我也懒得每次都输入,于是做出了修改:
- 检查源代码 第80行 - 97行 前后,找到这段代码,
并添加默认城市名,直接整段替换吧
:
class Tools:
def __init__(self):
self.citation = True
# 添加默认城市名,自行修改
self.default_location = "城市名称拼音"
pass
def get_future_weather_week(self, city: str = None) -> str:
"""
Get the weather for the next week for a given city.
:param city: The name of the city to get the weather for. If None, uses default location.
:return: The current weather information or an error message.
"""
# 如果未提供城市,则使用默认位置
if not city:
city = self.default_location
city_info = get_city_info(city)
if not city_info:
return f"""Could not find weather data for '{city}'. Please check the city name and try again."""
然后,接下来几行代码不动,直到下一个 def get_current_weather 字段,现在约在150行前后。找到def,if not,if not这几行,直接替换成这样就行了
。
def get_current_weather(self, city: str = None) -> str:
"""
Get the current weather for a given city.
:param city: The name of the city to get the weather for. If None, uses default location.
:return: The current weather information or an error message.
"""
if not city:
city = self.default_location
city_info = get_city_info(city)
if not city_info:
return """Error fetching weather data"""
这两段修改,第一个是修改默认地点,和“未来7天”的地理数据处理方式,第二个是修改“现在”的地理数据处理方式
已知问题
- 我发现很多同拼音城市,需要大家自己验证能不能正确读取,可能需要加具体地点名字才能搜索?或者需要具体的坐标代码
- 具体可以连代码一起发给Claude问问,似乎它能给出一套带有默认坐标系的代码修改方案来。
第二部分 - 调整天气模型 & 天气数据
1.1 原代码使用反人类的英制单位。必须调成公制才行。
- 经研究发现,网站默认公制单位,英制单位是作者强行加入的
。只要删除作者强改的部分就可以了。
还是找到未来和当前天气的数据部分:def get_future_weather_week 部分,和get_current_weather 部分。
- 去掉 未来天气 中的英制单位,约在120行左右看到如下:
"current": "temperature_2m",
"timezone": tmzone,
"temperature_unit": "fahrenheit",
"wind_speed_unit": "mph",
"precipitation_unit": "inch",
"forecast_days": 7,
删改成这样
:
"current": "temperature_2m",
"timezone": tmzone,
"forecast_days": 7,
- 去掉 此刻天气 中的英制单位,约在177行左右,看到如下:
"timezone": tmzone,
"temperature_unit": "fahrenheit",
"wind_speed_unit": "mph",
"precipitation_unit": "inch",
"forecast_days": 1,
删改成这样
:
"timezone": tmzone,
"forecast_days": 1,
现在,询问天气,应该就是公制单位了。
1.2 接下来做天气模型选择和个人气象数据偏好部分修改:
- 关于天气模型,在网站的 Data Sources 部分有详细介绍,点这里查看。世界顶级模型有中国气象局的 CMA GRAPES Global ,欧洲气象局的 ECMWF ,和美国气象局的 GFS 。各位佬自己选择哈。
- 手机 app 用 windy 习惯了,所以我选择 “ECMWF” 数据源。
-
找到 “Weather models”,选择一个天气模型,并选择。
-
然后到了自助餐时刻。你喜欢看哪些天气数据?气温?云量?下不下雨?下多少雨?刮多大风?找到“ Daily Weather Variables ”选择每日预报的数据, 找到 “Current Weather ” 选择想要的此刻的天气数据。
此时你可能需要翻译辅助。 -
最后到下方的 “API Response ” 部分,切换到 “Python” ,你会看到一段代码,找到
params后面的字段,大概长这样
:
-
params = {
"latitude": 52.52,
"longitude": 13.41,
"daily": ["weather_code", "temperature_2m_max", "temperature_2m_min"],
"models": "gfs_seamless",
"current": ["temperature_2m", "relative_humidity_2m", "apparent_temperature"],
daily :获取未来每日预报需要的数据
current :获取此刻天气需要数据
models :所采用的气象预报模型
- 把这几段代码分别填到原代码中。
比如,我采用“ECMWF”未来天气预报包含日出日落信息,气温,湿度,云量,等等,源代码未来天气params部分(约103行左右)被我改成这样(注意,latitude,longitude,timezone不要动):
params = {
"latitude": lat,
"longitude": lng,
"models": "ecmwf_ifs025",
"daily": [
"weather_code",
"sunrise",
"sunset",
"temperature_2m_max",
"temperature_2m_min",
"cloud_cover_mean",
"relative_humidity_2m_mean",
"precipitation_probability_max",
"wind_speed_10m_max",
"wind_gusts_10m_max",
"wind_direction_10m_dominant",
],
"current": "temperature_2m",
"timezone": tmzone,
"forecast_days": 7,
}
当前天气部分(约170行左右),我改成这样:
base_url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": lat,
"longitude": lng,
"models": "ecmwf_ifs025",
"current": [
"temperature_2m",
"apparent_temperature",
"relative_humidity_2m",
"cloud_cover",
"surface_pressure",
"wind_speed_10m",
"weather_code",
],
"timezone": tmzone,
"forecast_days": 1,
}
- 最后,未来天气预报部分,需要调整输出表格(其实就是输出表格内的字被我们改了,现在重新建立下表格)。这部分是
mapped_data字典(约132行左右)。
1. 方法:把之前调整后的整段代码发给 Claude3.7,让他帮我修改mapped_data部分。他几乎可以一次帮我们修改好。
2. 如果你直接采用我的数据,那直接复制下面代码替换就好了。
mapped_data = {
date: {
"weather_description": wmo_weather_codes[
str(data["daily"]["weather_code"][i])
],
"sunrise_sunset": f'Sunrise: {format_date(data["daily"]["sunrise"][i])} / Sunset: {format_date(data["daily"]["sunset"][i])}',
"temperature_max_min": f'{data["daily"]["temperature_2m_max"][i]} {data["daily_units"]["temperature_2m_max"]} / {data["daily"]["temperature_2m_min"][i]} {data["daily_units"]["temperature_2m_min"]}',
"cloud_cover": f'{data["daily"]["cloud_cover_mean"][i]} {data["daily_units"]["cloud_cover_mean"]}',
"humidity": f'{data["daily"]["relative_humidity_2m_mean"][i]} {data["daily_units"]["relative_humidity_2m_mean"]}',
"precipitation_probability": f'{data["daily"]["precipitation_probability_max"][i]} {data["daily_units"]["precipitation_probability_max"]}',
"wind": f'Speed: {data["daily"]["wind_speed_10m_max"][i]} {data["daily_units"]["wind_speed_10m_max"]} / Gusts: {data["daily"]["wind_gusts_10m_max"][i]} {data["daily_units"]["wind_gusts_10m_max"]} / Direction: {data["daily"]["wind_direction_10m_dominant"][i]} {data["daily_units"]["wind_direction_10m_dominant"]}',
}
for i, date in enumerate(data["daily"]["time"])
}
补充说明
OpenWebUI 的工具调用,需要LLM模型支持。就是需要一个LLM去调用这个工具读取数据,把数据返回给正在对话的LLM。由这个LLM对数据进行对话处理。这会导致 API 多一次调用。如果你的API是计次的,要注意用量。一般选一个便宜的,速度快的LLM就好了。不太推荐带推理的模型去获取,太慢了。
具体在“管理员面板 → 设置 → 界面 → 设置任务模型” 设置。本地模型由ollama提供,外部模型由API选择。
另外,任务模型似乎不能用2API(我猜)?


