从2025年10月18日LangChain正式发布了V1.0.0版本,这个版本可谓改动较大,但是对于开发人员受益颇多。
LangChain v1.0对命名空间进行了大刀阔斧的精简,将核心功能聚焦于Agent开发所需的基础组件,而将 legacy 功能迁移至langchain-classic包。这种精简带来了三个显著好处:
- 一是减少认知负担,新开发者不再需要面对数十个模块的选择困难;
- 二是降低安装体积,核心包大小减少60%;
- 三是提升运行效率,避免了不必要的依赖加载。
对于需要升级的项目,官方提供了平滑迁移路径,只需将旧代码中from langchain.legacy_xxx的导入替换为from langchain_classic.xxx即可。
下面我们用LangChain v1.0 快速搭建一个天气查询的智能体(ReAct Agent):
1. 模型准备
使用的模型采用之前文章中搭建的Ollama运行的本地qwen3:1.7b模型,具体模型的本地搭建可以参考这里。
2. 数据准备
要查询天气,就需要调用天气查询接口,我们选择百度地图的天气查询接口(每天5000次免费单次调用,每秒最多3次并发),但是这个接口需要传递district_id也就是行政区划码adcode,可以在这里下载。
但是下载的数据为csv格式,如果想提高查询效率,最好存储到数据库中,我们采用方便的sqlite数据库,用python完成数据入库:
python
## rw_csv_sql.py
from sqlalchemy import create_engine, func, select, or_
from sqlalchemy.orm import Session
from model import District, Base
engine = create_engine("sqlite:///app.db", echo=True)
Base.metadata.create_all(engine)
def getSession():
return Session(engine)
# 从CSV文件导入数据,跳过第一行
def import_csv():
with open("weather_district_id.csv", "r", encoding="utf-8") as f:
next(f) # 跳过第一行
for line in f:
(
district_id,
province,
city,
city_geocode,
district,
district_geocode,
lon,
lat,
) = line.strip().split(",")
district = District(
district_id=int(district_id),
province=province,
city=city,
city_geocode=city_geocode,
district=district,
district_geocode=district_geocode,
lon=float(lon),
lat=float(lat),
)
session = getSession()
session.add(district)
session.commit()
session.close()
def init_db():
with getSession() as sess:
stmt = select(func.count()).select_from(District)
result = sess.execute(stmt).scalar()
print(f"districts表中总行数为:{result}")
if result == 0:
import_csv()
# 从sqlite数据库查询数据
def test_query():
with getSession() as session:
stmt = select(District).where(District.district_id == 110100)
result = session.execute(stmt)
for row in result:
print(row)
def query_by_district_name(district_name):
with getSession() as session:
stmt = select(District).where(
or_(
District.district.like(f"%{district_name}%"),
District.district.like(f"%{district_name}区%"),
District.city.like(f"%{district_name}%"),
District.province.like(f"%{district_name}%"),
)
)
result = session.execute(
stmt, execution_options={"prebuffer_rows": True}
).scalars()
return result
然后就可以调用query_by_district_name(district_name)来根据区域名称获取对应的行政区划码。
由于百度天气接口有并发请求限制,所以我们需要进行限速处理:
python
## web_request.py
import httpx as requests
import orjson
from dotenv import dotenv_values, load_dotenv
import asyncio
from aiolimiter import AsyncLimiter
load_dotenv()
my_baidu_ak = dotenv_values()["BAIDU_AK"]
def get_weather(district_id):
try:
res = requests.get(
f"https://api.map.baidu.com/weather/v1/",
params={
"district_id": district_id,
"data_type": "all",
"ak": my_baidu_ak,
},
timeout=10,
)
if res:
# print(res.json())
data = res.json()
location = data["result"]["location"]
now = data["result"]["now"]
province = location["province"]
city = location["city"]
district = location["name"]
temp = now["temp"]
feels_like = now["feels_like"]
wind_dir = now["wind_dir"]
wind_class = now["wind_class"]
text = now["text"]
# print(
# f"{province} {city} {district} 温度为 {temp}℃, 体感温度为 {feels_like}℃, 风向为 {wind_dir}, 风力为 {wind_class}, 天气为 {text}"
# )
return f"{province} {city} {district} 温度为 {temp}℃, 体感温度为 {feels_like}℃, 风向为 {wind_dir}, 风力为 {wind_class}, 天气为 {text}"
# with open("baidu_weather.json", "w", encoding="utf-8") as f:
# f.write(
# orjson.dumps(res.json(), option=orjson.OPT_INDENT_2).decode("utf-8")
# )
except Exception as e:
print(f"请求百度天气API时出错: {e}")
# 最大并发数(例如限制同时最多 3 个请求,百度地图和高德地图给的天气查询默认最大并发就是3)
MAX_CONCURRENT = 3
# 信号量用于限流,但是不能限速(每 1 秒最多 3 个请求)
semaphore = asyncio.Semaphore(MAX_CONCURRENT)
# 创建限流器:并限速(每 1 秒最多 3 个请求,用1.1秒保证不会超速)
limiter = AsyncLimiter(max_rate=MAX_CONCURRENT, time_period=3.5)
# 异步发起请求
async def get_weather_async(client: requests.AsyncClient, district_id):
async with limiter: # 限制并发
try:
print(f"[{asyncio.get_event_loop().time():.2f}] 发起请求: {district_id}")
res = await client.get(
f"https://api.map.baidu.com/weather/v1/",
params={
"district_id": district_id,
"data_type": "all",
"ak": my_baidu_ak,
},
)
if res:
# print(res.json())
data = res.json()
location = data["result"]["location"]
now = data["result"]["now"]
province = location["province"]
city = location["city"]
district = location["name"]
temp = now["temp"]
feels_like = now["feels_like"]
wind_dir = now["wind_dir"]
wind_class = now["wind_class"]
text = now["text"]
print(
f"{province} {city} {district} 温度为 {temp}℃, 体感温度为 {feels_like}℃, 风向为 {wind_dir}, 风力为 {wind_class}, 天气为 {text}"
)
return f"{province} {city} {district} 温度为 {temp}℃, 体感温度为 {feels_like}℃, 风向为 {wind_dir}, 风力为 {wind_class}, 天气为 {text}"
# with open("baidu_weather.json", "w", encoding="utf-8") as f:
# f.write(
# orjson.dumps(res.json(), option=orjson.OPT_INDENT_2).decode("utf-8")
# )
except Exception as e:
print(f"请求百度天气API时出错: {e}")
async def get_weather_list_async(district_ids):
async with requests.AsyncClient() as client:
tasks = [get_weather_async(client, district_id) for district_id in district_ids]
result = await asyncio.gather(*tasks)
return result
if __name__ == "__main__":
results = asyncio.run(get_weather_list_async([370103, 370402]))
for i, res in enumerate(results):
if isinstance(res, Exception):
print(f"第{i+1}个请求出错: {res}")
else:
print(res)
通过封装的方法get_weather_list_async(district_ids)就可以进行优雅的并发调用了。
3. 编写智能体
下面就可以用LangChain v1.0 的API来编写ReAct类Agent了:
python
## main.py
"""
查询天气的Agent示例
"""
from langchain.agents import create_agent
from langchain.tools import tool
from rw_csv_sql import query_by_district_name
from web_request import get_weather_list_async
import asyncio
@tool
def query_weather(district_name: str) -> str:
"""查询指定地区的天气信息."""
name = get_district_from_input(district_name)
res = query_by_district_name(name)
district_ids = [row.district_id for row in res]
if len(district_ids) > 0:
results = asyncio.run(get_weather_list_async(district_ids))
store = [] # 存储结果
for i, reslt in enumerate(results):
if isinstance(reslt, Exception):
print(f"第{i+1}个请求出错: {reslt}")
else:
# print(reslt)
store.append(reslt)
return "\n".join(store)
agent = create_agent(
model=ChatOllama(
model="qwen3:1.7b",
base_url="http://127.0.0.1:18080",
temperature=0.7,
top_p=0.9,
max_tokens=1024,
),
tools=[query_weather],
system_prompt="你是一个贴心助手,当需要查询天气时,请调用query_weather工具.",
)
result = agent.invoke(
{
"messages": [
# {"role": "user", "content": "吉林长春朝阳区的天气如何?"},
# {"role": "user", "content": "朝阳位于长春市,取长春朝阳区天气"},
# {"role": "user", "content": "杭州西湖区天气如何?"},
{"role": "user", "content": "济南市中区天气如何?"},
]
}
)
print("天气智能体结果:", result)
结果显示:
shell
天气智能体结果: {'messages': [HumanMessage(content='济南市中区天气如何?', additional_kwargs={}, response_metadata={}, id='1b268a33-2748-4b07-983e-b1dda44e0d0a'), AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'qwen3:1.7b', 'created_at': '2025-11-18T02:11:45.164919911Z', 'done': True, 'done_reason': 'stop', 'total_duration': 15255926426, 'load_duration': 53532119, 'prompt_eval_count': 160, 'prompt_eval_duration': 74933566, 'eval_count': 246, 'eval_duration': 15034951707, 'model_name': 'qwen3:1.7b', 'model_provider': 'ollama'}, id='lc_run--3c3dfabc-97ac-45bf-bb5f-096714d05f18-0', tool_calls=[{'name': 'query_weather', 'args': {'district_name': '
济南市'}, 'id': '19bce1b6-5de7-486b-8ffa-1d007adf49fa', 'type': 'tool_call'}], usage_metadata={'input_tokens': 160, 'output_tokens': 246, 'total_tokens': 406}), ToolMessage(content='山东省 济南市 济南市 温度为 2℃, 体感温度为 0℃, 风向为 西南风, 风力为 2级, 天气为 晴\n山东省 济南市 历下区 温度为 2℃, 体感温度为 0℃, 风向为 西南风, 风力为 2
级, 天气为 晴\n山东省 济南市 市中区 温度为 2℃, 体感温度为 0℃, 风向为 西南风, 风力为 2级, 天气为 晴\n山东省 济南市 槐荫区 温度为 2℃, 体感温度为 0℃, 风向为 西南风, 风力为
2级, 天气为 晴\n山东省 济南市 天桥区 温度为 2℃, 体感温度为 0℃, 风向为 西南风, 风力为 2级, 天气为 晴\n山东省 济南市 历城区 温度为 2℃, 体感温度为 0℃, 风向为 西南风, 风力为
2级, 天气为 晴\n山东省 济南市 长清区 温度为 2℃, 体感温度为 0℃, 风向为 东南风, 风力为 2级, 天气为 晴\n山东省 济南市 章丘区 温度为 1℃, 体感温度为 -1℃, 风向为 西风, 风力为
3级, 天气为 晴\n山东省 济南市 济阳区 温度为 2℃, 体感温度为 0℃, 风向为 西风, 风力为 2级, 天气为 晴\n山东省 济南市 莱芜区 温度为 0℃, 体感温度为 -2℃, 风向为 西风, 风力为 1级, 天气为 晴\n山东省 济南市 钢城区 温度为 0℃, 体感温度为 -1℃, 风向为 西北风, 风力为 2级, 天气为 晴\n山东省 济南市 平阴县 温度为 2℃, 体感温度为 0℃, 风向为 西风, 风力为 2级, 天气为 晴\n山东省 济南市 商河县 温度为 0℃, 体感温度为 -1℃, 风向为 西风, 风力为 2级, 天气为 晴', name='query_weather', id='3e27e63d-3aa3-4a59-89cf-764183e79ee4', tool_call_id='19bce1b6-5de7-486b-8ffa-1d007adf49fa'), AIMessage(content='济南市中区当前天气情况如下:\n- 温度:2℃,体感温度0℃\n- 风向:西南风,风力2级\n- 天气:晴\n\n请注意
,济南市中区属于济南市辖区,天气情况与全市一致。', additional_kwargs={}, response_metadata={'model': 'qwen3:1.7b', 'created_at': '2025-11-18T02:12:24.778127314Z', 'done': True, 'done_reason': 'stop', 'total_duration': 26410779795, 'load_duration': 62636259, 'prompt_eval_count': 879, 'prompt_eval_duration': 4674770637, 'eval_count': 310, 'eval_duration': 21565468048, 'model_name': 'qwen3:1.7b', 'model_provider': 'ollama'}, id='lc_run--0f4ee469-8797-4f3a-b39f-48c349aecc87-0', usage_metadata={'input_tokens': 879, 'output_tokens': 310, 'total_tokens': 1189})]}
从结果中,可以看到最终的AIMessage正是我们需要的:
bash
AIMessage(content='济南市中区当前天气情况如下:\n- 温度:2℃,体感温度0℃\n- 风向:西南风,风力2级\n- 天气:晴\n\n请注意
,济南市中区属于济南市辖区,天气情况与全市一致。'
4. 优化空间
在这个简单的天气查询智能体中,让大模型根据我们的问题,进行思考推理并调用天气查询工具,返回我们想要的结果,但是其实还可以采用MCP的方式,把这里的调用天气查询工具,改成调用百度MCP服务,也是可以的。这个就是后面可以优化的空间。