1. 前言
第 3 篇跑通了第一个 Agent,能查天气了。但当时用的是硬编码的模拟数据,,根本不是真正的天气。
这一篇我决定全部用真实 API——能查真实天气、能搜实时新闻、能读网页内容。这些工具写好之后,后面可以直接打包进更大的项目里。
这一篇的代码,每一行都是能真正跑起来干活的。
2. 先设计三个实用的工具
我选了三个方向:
| 工具 | 用的 API | 是否免费 | 需要 API Key |
|---|---|---|---|
| 查天气 | Open-Meteo | 免费 | 不需要 |
| 搜新闻 | NewsAPI | 免费版每天 100 次 | 需要(免费注册) |
| 读网页 | requests + BeautifulSoup | 免费 | 不需要 |
这三个工具覆盖了"外部数据获取"的核心场景,后面做任何项目基本都能用上。
3. 工具函数(可以单独 import 使用)
我先把每个工具写成独立的函数,这样以后可以直接 import 到其他项目里用。
3.1 查天气(真实 API)
import requests
# 天气数据工具:可以单独 import 使用
def get_weather(city: str) -> str:
"""
查询指定城市的实时天气
使用 Open-Meteo API(免费,无需 API Key)
步骤:城市名 → 经纬度 → 天气数据
"""
try:
# 第 1 步:城市名转经纬度
geo_url = f"https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1&language=zh"
geo_resp = requests.get(geo_url, timeout=10)
geo_data = geo_resp.json()
if "results" not in geo_data or len(geo_data["results"]) == 0:
return f"未找到城市:{city}"
lat = geo_data["results"][0]["latitude"]
lon = geo_data["results"][0]["longitude"]
city_name = geo_data["results"][0].get("name", city)
# 第 2 步:查询天气
weather_url = (
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={lat}&longitude={lon}"
f"¤t_weather=true"
f"&hourly=temperature_2m,relative_humidity_2m,weathercode"
f"&timezone=auto"
)
weather_resp = requests.get(weather_url, timeout=10)
weather_data = weather_resp.json()
current = weather_data.get("current_weather", {})
temp = current.get("temperature", "N/A")
wind_speed = current.get("windspeed", "N/A")
# 天气代码转中文描述
weather_code = current.get("weathercode", 0)
code_map = {
0: "晴天", 1: "大部晴朗", 2: "多云", 3: "阴天",
45: "雾", 48: "雾凇",
51: "小毛毛雨", 53: "中毛毛雨", 55: "大毛毛雨",
61: "小雨", 63: "中雨", 65: "大雨",
71: "小雪", 73: "中雪", 75: "大雪",
80: "小阵雨", 81: "中阵雨", 82: "大阵雨",
95: "雷暴", 96: "雷暴加冰雹", 99: "强雷暴加冰雹"
}
weather_desc = code_map.get(weather_code, f"代码 {weather_code}")
return f"{city_name}:{temp}°C,{weather_desc},风速 {wind_speed} km/h"
except Exception as e:
return f"查询天气失败:{str(e)}"
这个函数做了什么:
- 输入城市名(如"北京")
- 调用 Open-Meteo Geocoding API 把城市名转成经纬度
- 调用 Open-Meteo Weather API 查实时天气
- 把天气代码转成中文描述
- 返回格式化结果
不需要任何 API Key,直接就能用。
3.2 搜新闻(实时新闻 API)
import os
def get_news(query: str, api_key: str = None) -> str:
"""
搜索最新新闻
使用 NewsAPI(免费版每天 100 次请求)
需要去 https://newsapi.org/register 免费注册获取 API Key
"""
try:
key = api_key or os.getenv("NEWS_API_KEY")
if not key:
return "请先设置 NEWS_API_KEY 环境变量,或传入 api_key 参数"
url = "https://newsapi.org/v2/everything"
params = {
"q": query,
"language": "zh",
"sortBy": "publishedAt",
"pageSize": 5,
"apiKey": key
}
resp = requests.get(url, params=params, timeout=10)
data = resp.json()
if data.get("status") != "ok":
return f"新闻查询失败:{data.get('message', '未知错误')}"
articles = data.get("articles", [])
if not articles:
return f"没有找到关于「{query}」的新闻"
result = [f"关于「{query}」的最新新闻:"]
for i, article in enumerate(articles[:5], 1):
title = article.get("title", "无标题")
source = article.get("source", {}).get("name", "未知来源")
url = article.get("url", "")
result.append(f"{i}. [{source}] {title}")
result.append(f" {url}")
return "\n".join(result)
except Exception as e:
return f"搜索新闻失败:{str(e)}"
注意:这个需要去 newsapi.org 免费注册拿到 API Key,然后设置到环境变量 NEWS_API_KEY 里。免费版每天 100 次,学习完全够用。
3.3 读网页内容
from bs4 import BeautifulSoup
def fetch_webpage(url: str) -> str:
"""
获取网页内容并提取正文文本
使用 requests + BeautifulSoup,无需 API Key
可以读取任意公开网页的内容
"""
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
}
resp = requests.get(url, headers=headers, timeout=15)
resp.encoding = resp.apparent_encoding
soup = BeautifulSoup(resp.text, "html.parser")
# 移除 script 和 style 标签
for tag in soup(["script", "style", "nav", "footer", "header"]):
tag.decompose()
text = soup.get_text(separator="\n", strip=True)
# 限制返回长度,避免 Token 爆炸
max_chars = 2000
if len(text) > max_chars:
text = text[:max_chars] + "...(内容已截断)"
return text if text else "无法提取网页内容"
except Exception as e:
return f"读取网页失败:{str(e)}"
这个工具不需要任何 API Key,可以读取任意公开网页的内容。后面做知识库、做日报汇总、做信息采集都能用上。
4. 给 LLM 注册工具
工具函数写好了,接下来注册给 LLM。注意看 description 字段,写得不叫很详细,这是为了让 LLM 能准确判断什么时候该用哪个工具。
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询任意城市的实时天气,包括温度、天气状况、风速。适合用户问天气、温度、冷不冷、热不热等情况时调用",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海、广州、纽约、东京等"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_news",
"description": "搜索指定话题的最新新闻,返回新闻标题和链接。适合用户问"最近有什么新闻"、"搜索一下xxx"等情况时调用",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,如人工智能、天气预报、NBA"
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "fetch_webpage",
"description": "读取指定网页的内容并提取正文文本。适合用户问"打开这个网页"、"看看这篇文章写了什么"等情况时调用",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "网页的完整 URL,必须以 http:// 或 https:// 开头"
}
},
"required": ["url"]
}
}
}
]
5. 完整代码
"""
多工具 Agent
功能:查天气(真实API)+ 搜新闻(真实API)+ 读网页
"""
from openai import OpenAI
import json
import os
import requests
from bs4 import BeautifulSoup
from dotenv import load_dotenv
# ============ 1. 加载配置 ============
load_dotenv()
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL")
)
# ============ 2. 工具函数 ============
def get_weather(city: str) -> str:
"""查询指定城市的实时天气(Open-Meteo API,免费,无需 API Key)"""
try:
# 城市名 → 经纬度
geo_url = f"https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1&language=zh"
geo_resp = requests.get(geo_url, timeout=10)
geo_data = geo_resp.json()
if "results" not in geo_data or len(geo_data["results"]) == 0:
return f"未找到城市:{city}"
lat = geo_data["results"][0]["latitude"]
lon = geo_data["results"][0]["longitude"]
city_name = geo_data["results"][0].get("name", city)
# 经纬度 → 天气
weather_url = (
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={lat}&longitude={lon}"
f"¤t_weather=true&hourly=temperature_2m,relative_humidity_2m,weathercode"
f"&timezone=auto"
)
weather_resp = requests.get(weather_url, timeout=10)
weather_data = weather_resp.json()
current = weather_data.get("current_weather", {})
temp = current.get("temperature", "N/A")
wind_speed = current.get("windspeed", "N/A")
code_map = {
0: "晴天", 1: "大部晴朗", 2: "多云", 3: "阴天",
45: "雾", 51: "小毛毛雨", 53: "中毛毛雨", 55: "大毛毛雨",
61: "小雨", 63: "中雨", 65: "大雨",
71: "小雪", 73: "中雪", 75: "大雪",
80: "小阵雨", 81: "中阵雨", 82: "大阵雨",
95: "雷暴"
}
weather_desc = code_map.get(current.get("weathercode", 0), "未知")
return f"{city_name}:{temp}°C,{weather_desc},风速 {wind_speed} km/h"
except Exception as e:
return f"查询天气失败:{str(e)}"
def get_news(query: str) -> str:
"""搜索最新新闻(NewsAPI,免费版每天 100 次)"""
try:
api_key = os.getenv("NEWS_API_KEY")
if not api_key:
return "请先注册 https://newsapi.org/register 获取 API Key,并设置 NEWS_API_KEY 环境变量"
url = "https://newsapi.org/v2/everything"
params = {
"q": query,
"language": "zh",
"sortBy": "publishedAt",
"pageSize": 5,
"apiKey": api_key
}
resp = requests.get(url, params=params, timeout=10)
data = resp.json()
if data.get("status") != "ok":
return f"新闻查询失败:{data.get('message', '未知错误')}"
articles = data.get("articles", [])
if not articles:
return f"没有找到关于「{query}」的新闻"
result = [f"关于「{query}」的最新新闻:"]
for i, article in enumerate(articles[:5], 1):
title = article.get("title", "无标题")
source = article.get("source", {}).get("name", "未知来源")
url = article.get("url", "")
result.append(f"{i}. [{source}] {title}")
result.append(f" {url}")
return "\n".join(result)
except Exception as e:
return f"搜索新闻失败:{str(e)}"
def fetch_webpage(url: str) -> str:
"""读取网页内容(requests + BeautifulSoup,无需 API Key)"""
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36"
}
resp = requests.get(url, headers=headers, timeout=15)
resp.encoding = resp.apparent_encoding
soup = BeautifulSoup(resp.text, "html.parser")
for tag in soup(["script", "style", "nav", "footer", "header"]):
tag.decompose()
text = soup.get_text(separator="\n", strip=True)
if len(text) > 2000:
text = text[:2000] + "...(内容已截断)"
return text if text else "无法提取网页内容"
except Exception as e:
return f"读取网页失败:{str(e)}"
def execute_tool(name: str, args: dict) -> str:
"""根据工具名执行对应的函数"""
if name == "get_weather":
return get_weather(args["city"])
elif name == "get_news":
return get_news(args["query"])
elif name == "fetch_webpage":
return fetch_webpage(args["url"])
else:
return f"未知工具:{name}"
# ============ 3. 工具描述 ============
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询任意城市的实时天气,包括温度、天气状况、风速。适合用户问天气、温度、冷不冷等情况时调用",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称,如北京、上海"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_news",
"description": "搜索指定话题的最新新闻,返回新闻标题和链接。适合用户问"最近有什么新闻"、"搜索一下xxx"等情况时调用",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词,如人工智能、NBA"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "fetch_webpage",
"description": "读取指定网页的内容并提取正文文本。适合用户问"打开这个网页"、"看看这篇文章写了什么"等情况时调用",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "description": "网页完整 URL,以 http:// 或 https:// 开头"}
},
"required": ["url"]
}
}
}
]
# ============ 4. 对话循环 ============
def chat_with_agent(user_input: str) -> str:
"""和 Agent 对话,支持多轮工具调用"""
messages = [{"role": "user", "content": user_input}]
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools,
tool_choice="auto"
)
msg = response.choices[0].message
while msg.tool_calls:
messages.append(msg)
for tool_call in msg.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f" 🧠 调用工具:{name}({args})")
result = execute_tool(name, args)
print(f" 🛠 返回结果:{result[:100]}...")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools,
tool_choice="auto"
)
msg = response.choices[0].message
return msg.content
# ============ 5. 测试 ============
if __name__ == "__main__":
questions = [
"北京的天气怎么样?",
"最近人工智能有什么新闻?",
"帮我查一下上海的天气,再搜一下机器学习的新闻",
]
for q in questions:
print(f"\n{'='*50}")
print(f"👤 用户:{q}")
print(f"{'='*50}")
answer = chat_with_agent(q)
print(f"🤖 Agent:{answer}")
6. 代码注意内容
- 安装依赖:
pip install openai python-dotenv requests beautifulsoup4
- 在
.env文件里配置:
OPENAI_API_KEY=你的API密钥
OPENAI_BASE_URL=https://api.deepseek.com # 如果用 DeepSeek
NEWS_API_KEY=你的NewsAPI密钥 # 去 https://newsapi.org/register 免费注册

329

被折叠的 条评论
为什么被折叠?



