用LangChain1.0搭建第一个天气查询智能体

从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服务,也是可以的。这个就是后面可以优化的空间。

相关推荐
菠菠萝宝5 小时前
【Java手搓RAGFlow】-3- 用户认证与权限管理
java·开发语言·人工智能·llm·openai·qwen·rag
破烂pan11 小时前
lmdeploy.pytorch 新模型支持代码修改
python·深度学习·llm·lmdeploy
在未来等你13 小时前
AI Agent设计模式 Day 7:Tree-of-Thoughts模式:树形思维探索
设计模式·llm·react·ai agent·plan-and-execute
FreeCode16 小时前
使用LangSmith评估智能体
python·langchain·agent
小兵张健17 小时前
LLM 四阶段和 Transformer 架构(一)
llm
FreeCode17 小时前
使用LangSmith追踪智能体运行
python·langchain·agent
间彧17 小时前
milvus向量数据库详解与应用实战
llm
间彧17 小时前
ChromaDB详解与应用实战
llm
智泊AI18 小时前
一文讲清:AI大模型中AI Agent的定义、分类及发展趋势
llm