0%

LangChain学习日志: Agent

尝试使用LangChain的Agents功能。

Intro

LangChain 官方文档的 Core components 第一项就是 Agents ,我们先来看看官方是如何介绍的 :

Agents combine language models with tools to create systems that can reason about tasks, decide which tools to use, and iteratively work towards solutions.

create_agent provides a production-ready agent implementation.

An LLM Agent runs tools in a loop to achieve a goal. An agent runs until a stop condition is met - i.e., when the model emits a final output or an iteration limit is reached.

这段话告诉我们三个信息:

  • Agent 是将语言模型(LLM)与工具(Tools)结合的系统。它的特点是具备推理能力,能自主决定该用什么工具,并通过反复迭代来解决任务。
  • 可以使用 create_agent 函数实现
  • Agent 的运行是一个循环过程:它会不断运行工具,直到满足停止条件为止。

并且给出了简单的工作流程:

Core components

Model

Model 就是 Agent 的推理引擎,负责思考、决策和生成回复。
对于 LLM 模型的调用分为两种模式:

  • Static Model:最常用的方式。在创建 Agent 时就固定好(例如指定用 gpt-4o),整个运行过程中不会变。
  • Dynamic Model:一种高级用法,可以在运行时根据上下文动态选择模型。比如在简单的问题上使用便宜的模型。

Tools

Tools 就是 Agent 与外部世界交互的工具。通常是标准的 Python 函数,加上 @tool 装饰器。根据文档的示例,我们可以通过create_agent简单定义一个 Tool :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from langchain.tools import tool
from langchain.agents import create_agent


@tool
def search(query: str) -> str:
"""Search for information."""
return f"Results for: {query}"

@tool
def get_weather(location: str) -> str:
"""Get weather information for a location."""
return f"Weather in {location}: Sunny, 72°F"

agent = create_agent(model, tools=[search, get_weather])

而如果要自定义Tool错误的处理方式,就使用 @wrap_tool_call 装饰器创建中间件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage


@wrap_tool_call
def handle_tool_errors(request, handler):
"""Handle tool execution errors with custom messages."""
try:
return handler(request)
except Exception as e:
# Return a custom error message to the model
return ToolMessage(
content=f"Tool error: Please check your input and try again. ({str(e)})",
tool_call_id=request.tool_call["id"]
)

agent = create_agent(
model="gpt-4o",
tools=[search, get_weather],
middleware=[handle_tool_errors]
)

System Prompt

顾名思义,System Prompt 就是系统层面的提示词,用于设定 Agent 的行为规范和角色。
最简单的定义方式就是在构建agent示例时传入:

1
2
3
4
5
agent = create_agent(
model,
tools,
system_prompt="You are a helpful assistant. Be concise and accurate."
)

这是传入Str,复杂一些,可以传入LangChain的  SystemMessage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from langchain.agents import create_agent
from langchain.messages import SystemMessage, HumanMessage

literary_agent = create_agent(
model="anthropic:claude-sonnet-4-5",
system_prompt=SystemMessage(
content=[
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing literary works.",
},
{
"type": "text",
"text": "<the entire contents of 'Pride and Prejudice'>",
"cache_control": {"type": "ephemeral"}
}
]
)
)

result = literary_agent.invoke(
{"messages": [HumanMessage("Analyze the major themes in 'Pride and Prejudice'.")]}
)

Invocation

启动 Agent。

Agent 的运行是基于 State (状态) 的更新,通常只需要给它传入一个新的消息(Message),它就会开始根据图(Graph)的逻辑运行,直到得出结果。比如:

1
2
3
result = agent.invoke(
{"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}
)

除了以上基础的必要的步骤,LangChain 还为 Agents 提供了一些高级功能。我们简单了解一下:
Structured Output (结构化输出)
可以强制返回 JSON 数据(比如提取出的姓名、日期、订单号),方便程序直接后续处理。

Memory (记忆机制)

  • 短期记忆: 通过 State 实现记住这轮对话里你刚才说了什么。
  • 长期记忆: 通过数据库实现历史记忆。

Streaming (流式输出)
Agent 可以一边调用工具,一边就在屏幕上即时打印出它的思考过程。

Middleware (中间件)
它可以在 Agent 说话前、说话后,或者工具报错时工作。比如在工具报错时,自动让 Agent 重试,而不是直接让程序崩溃。

这些Agents的功能似乎封装的还不错,那我们来实际尝试一下。

实战

在实战中,我们需要实现一个日常小助手,他有以下两个小功能:

🤖Daily Assistant

WeatherTool: 查询某地天气

Loan Calculator: 计算等额本息

WeatherTool

这是一个天气查询工具。为了查询真实的天气,我们去 OpenWeatherMap 官网申请一个免费的 API Key,然后使用其api查询指定城市的天气。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import os
import requests
from typing import Type, Optional
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field

# 1. 定义输入参数的结构
class WeatherInput(BaseModel):
city: str = Field(
description="需要查询天气的城市英文名,例如 'Beijing', 'London', 'New York'"
)

# 2. 定义工具类
class RealWeatherTool(BaseTool):
name: str = "get_real_weather"
description: str = (
"获取指定城市的实时天气详情。包含温度、天气状况、湿度等信息。"
"当用户询问'今天天气如何'或'下雨吗'时使用此工具。"
)
args_schema: Type[BaseModel] = WeatherInput
api_key: str = os.getenv("OPENWEATHER_API_KEY", "******")

def _run(self, city: str) -> str:
base_url = "http://api.openweathermap.org/data/2.5/weather"
params = {
"q": city,
"appid": self.api_key,
"units": "metric",
"lang": "zh_cn"
}

try:
response = requests.get(base_url, params=params)
response.raise_for_status()

data = response.json()

weather_desc = data["weather"][0]["description"]
temp = data["main"]["temp"]
humidity = data["main"]["humidity"]
wind_speed = data["wind"]["speed"]

return (
f"【{city} 实时天气】\n"
f"- 状况: {weather_desc}\n"
f"- 温度: {temp}°C\n"
f"- 湿度: {humidity}%\n"
f"- 风速: {wind_speed} m/s"
)

except requests.exceptions.HTTPError as e:
if response.status_code == 404:
return f"Error: 找不到城市 '{city}',请检查拼写。"
elif response.status_code == 401:
return "Error: API Key 无效。"
return f"Error: 请求失败 ({str(e)})"
except Exception as e:
return f"Error: 查询过程中发生未知错误: {str(e)}"

在上述代码中,我们先定义了输入参数的结构WeatherInput,其实就是输入一个城市名字符串。随后,继承BaseTool类定义一个RealWeatherTool类,定义内部字段并重写_run方法。

和上一篇笔记一样,我们使用阿里的 qwen 模型,创建一个agent,并把自定义的tool交给他测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os
from langchain_community.chat_models import ChatTongyi
from langchain.agents import create_agent
from tools.weathertool import RealWeatherTool
from langchain_core.messages import HumanMessage

os.environ["DASHSCOPE_API_KEY"] = "******"
llm = ChatTongyi(model_name="qwen-max")

weather_tool = RealWeatherTool()
tools = [weather_tool]

agent = create_agent(llm, tools=tools)

query = "帮我查查杭州的天气。"
result = agent.invoke({
"messages": [HumanMessage(content=query)]
})

for message in result["messages"]:
message.pretty_print()

我们打印完整的输出,记录了大模型调用工具的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
=============== Human Message ===============
帮我查查杭州的天气。
=============== Ai Message ================
Tool Calls:
get_real_weather (call_9a7740f492704862b7ae77)
Call ID: call_9a7740f492704862b7ae77
Args:
city: Hangzhou
================ Tool Message ===============
Name: get_real_weather
【Hangzhou 实时天气】
- 状况: 晴
- 温度: 8.95°C
- 湿度: 28%
- 风速: 3.94 m/s
================= Ai Message ===============
杭州现在的天气情况如下:
- 状况: 晴
- 温度: 8.95°C
- 湿度: 28%
- 风速: 3.94 m/s
天气晴朗,外出注意防晒哦!

Loan Calculator

这一部分我们做一个简单的贷款计算器,以实现精准的计算,这是 LLM 不具有的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from typing import Type
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
import math

# 1. 定义输入模型
class LoanInput(BaseModel):
principal: float = Field(description="贷款本金总额,单位为元。例如 1000000")
annual_rate_percent: float = Field(description="年利率百分比。例如 3.5 代表 3.5%")
years: int = Field(description="贷款期限(年)。例如 30")

# 2. 定义贷款计算工具
class LoanCalculatorTool(BaseTool):
name: str = "calculate_loan_payment"
description: str = (
"专业的贷款计算器(等额本息方式)。"
"当用户询问'房贷月供'、'车贷计算'、'分期付款利息'时使用此工具。"
"必须使用此工具进行计算,禁止模型自行估算。"
)
args_schema: Type[BaseModel] = LoanInput

def _run(self, principal: float, annual_rate_percent: float, years: int) -> str:
try:
# 数据预处理
monthly_rate = annual_rate_percent / 100 / 12
total_months = years * 12

# 核心计算逻辑
if monthly_rate == 0:
monthly_payment = principal / total_months
else:
numerator = principal * monthly_rate * math.pow(1 + monthly_rate, total_months)
denominator = math.pow(1 + monthly_rate, total_months) - 1
monthly_payment = numerator / denominator

# 计算总额
total_payment = monthly_payment * total_months
total_interest = total_payment - principal

return (
f" 贷款本金: {principal:,.2f} 元\n"
f" 年利率: {annual_rate_percent}%\n"
f" 贷款期限: {years} 年 ({total_months} 期)\n"
f" 每月月供: {monthly_payment:,.2f} 元\n"
f" 总利息: {total_interest:,.2f} 元\n"
f" 本息合计: {total_payment:,.2f} 元"
)
except Exception as e:
return f"计算出错: {str(e)}"

只要在调用时示例化并加入 Tool 列表即可:

1
2
calculator_tool = LoanCalculatorTool()
tools = [weather_tool, calculator_tool]

Test

我们对上述 Agent 进行一个简单的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
================ Human Message ===============

贷 100 万,30 年,利率 3.25%,每月月供多少?另外,北京今天天气如何?
================ Ai Message ===============
Tool Calls:
calculate_loan_payment (call_2f9e1a62ae0f42cab0eabd)
Call ID: call_2f9e1a62ae0f42cab0eabd
Args:
principal: 1000000
annual_rate_percent: 3.25
years: 30
get_real_weather (call_05f08c54e13747b4b6513d)
Call ID: call_05f08c54e13747b4b6513d
Args:
city: Beijing
================ Tool Message ================
Name: calculate_loan_payment

贷款本金: 1,000,000.00 元
年利率: 3.25%
贷款期限: 30 年 (360 期)
每月月供: 4,352.06 元
总利息: 566,742.75 元
本息合计: 1,566,742.75 元
================ Tool Message ================
Name: get_real_weather

【Beijing 实时天气】
- 状况: 晴
- 温度: -4.06°C
- 湿度: 17%
- 风速: 7.74 m/s
================= Ai Message ================

您的贷款每月月供为 4,352.06 元。下面是详细信息:
- 贷款本金: 1,000,000.00 元
- 年利率: 3.25%
- 贷款期限: 30 年 (360 期)
- 总利息: 566,742.75 元
- 本息合计: 1,566,742.75 元

北京今天的实时天气情况如下:
- 状况: 晴
- 温度: -4.06°C
- 湿度: 17%
- 风速: 7.74 m/s

请注意保暖,外出时可能需要携带外套。
🌙