👋 欢迎阅读

🏠个人主页: 愿旖旎
📘专栏传送门: 算法专栏
💻当前学习内容:LangChain
📑 目录
-
[三、LangChain 相关概念](#三、LangChain 相关概念)
一、前置讲解
在进入正文前,先花 10 秒了解本篇会反复用到的核心概念,带着印象去读正文,学习效率更高。
🧠 前置知识一:ChatOpenAI 与 DeepSeek
用 OpenAI 兼容接口定义聊天模型,是 LangChain 接入大模型的第一步。
📐 前置知识二:消息类型(System/Human/AI/Tool)
模型输入输出的统一格式------系统消息、用户消息、AI 回复、工具结果。
📚 前置知识三:Runnable 接口
LangChain 组件的统一标准接口,支持调用、批处理、流式、组合。
🔗 前置知识四:LCEL 链式(|)
用管道符把多个组件串成一条链,一次性执行得到最终结果。
🛠️ 前置知识五:工具调用(Tool)
让模型具备与外部世界交互的能力,是 Agent 的基础。
二、定义大模型
2.1 安装 AI 包和定义大模型
cpp
pip install -U langchain-deepseek # 安装 AI 包
# 定义大模型
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="deepseek-chat")
2.2 定义消息列表和调用大模型
cpp
# 定义消息列表
from langchain_core.messages import HumanMessage, SystemMessage
messages = [
SystemMessage(content="Translate the following from English into Chinese"),
HumanMessage(content="I am QiLongHao!"),
]
LangChain 中的三类基础消息:
| 消息类型 | 角色 | 说明 |
|---|---|---|
| SystemMessage | 系统角色 | 系统消息通常作为输入消息序列的第一条 ,用来启动 AI 行为 |
| HumanMessage | 用户角色 | 来自用户、从用户传递到模型的消息 |
| AIMessage | AI 角色 | 聊天模型回答的内容 |
调用大模型:
cpp
result = model.invoke(messages)
print(result.content)
model 是 LangChain Runnable (可运行)接口的实例,这意味着 model 提供了一个标准接口供我们与之交互 。要简单地调用模型,我们可以将消息列表传递给 .invoke 方法 。
AIMessage 的四个核心属性:
| 属性 | 说明 |
|---|---|
| content | 消息的内容(文本) |
| additional_kwargs | 与消息关联的其他有效负载数据 ,如模型提供商编码的工具调用 |
| response_metadata | 响应元数据(请求 ID、模型版本、响应头等),侧重"响应"本身,主要用于调试与日志 |
| usage_metadata | 使用元数据(Token 计数),侧重"资源消耗",主要用于成本计算与监控 |
只输出字符串(StrOutputParser):
cpp
# 定义 str 字符串输出解析器
from langchain_core.output_parsers import StrOutputParser
result = model.invoke(messages)
parser = StrOutputParser()
print(parser.invoke(result))
若只想输出聊天模型返回的结果字符串,可以使用 StrOutputParser 输出解析器组件 ,将大模型输出结果解析为最可能的字符串。
2.3 链式执行
通过上述步骤可以发现,无论是调用大模型,还是输出解析 ,每次都调用了 invoke() 方法,最终才能得到想要的结果。
对于 LangChain 而言,它提供了链式执行的能力 ------只需定义各个组件 ,将它们"链起来" ,一次性执行即可得到最终效果 。
cpp
import os
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
# 定义大模型
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
# 定义消息列表
messages = [
SystemMessage(content="Translate the following from English into Chinese"),
HumanMessage(content="hi!"),
]
# 定义输出解析器
parser = StrOutputParser()
# 定义链:模型输出 -> 字符串解析
chain = model | parser
# 执行链
result = chain.invoke(messages)
print(result)
三、LangChain 相关概念
3.1 Runnable 接口
Runnable 接口是使用 LangChain Components(组件)的基础。
概念说明 :Components(组件)是在构建应用程序时提供的一系列核心构建块,例如语言模型、输出解析器、检索器、编译的 LangGraph 图等。
Runnable 定义了一个标准接口,允许 Runnable 组件:
| 能力 | 说明 |
|---|---|
| Invoked(调用) | 单个输入转换为输出 |
| Batched(批处理) | 多个输入被有效地转换为输出 |
| Streamed(流式传输) | 输出在生成时进行流式传输 |
| Inspected(检查) | 可以访问有关 Runnable 的输入、输出和配置的原理图信息 |
| Composed(组合) | 可以组合多个 Runnable,使用 LCEL 协同工作创建复杂管道 |
所有能力参考官方文档。
因此,在快速上手中,我们定义的语言模型(model)、输出解析器(StrOutputParser)都是 Runnable 接口的实例 !他们都使用了 Invoked(调用) 的能力。
python
# 语言模型(model)
model = ChatOpenAI(model="deepseek-chat")
result = model.invoke(messages) # 语言模型是 Runnable 接口实例,允许 invoke 调用
# 输出解析器(StrOutputParser)
parser = StrOutputParser()
parser.invoke(result) # 输出解析器是 Runnable 接口实例,允许 invoke 调用
重点 :Runnable 解决了原生大模型的一个问题------切换大模型困难 ,因为它是一个标准接口 ,因此不会因为换模型导致发生一系列连锁反应。
3.2 LCEL
LangChain Expression Language(LCEL) :采用声明性方法 ,从现有 Runnable 对象构建新的 Runnable 对象。
通过 LCEL 构建出的新 Runnable 对象,被称为 RunnableSequence ,表示可运行序列 。RunnableSequence 就是一种链 ------通过调试就能发现,chain 的类型就是 RunnableSequence。
重要的是,RunnableSequence 也是 Runnable 接口 的实例,它实现了完整的 Runnable 接口,因此它可以与其他任何 Runnable 以相同的方式使用。
python
chain = model | parser
chain.invoke(messages) # 链是 Runnable 接口实例,允许 invoke 调用
核心机制 :上一个 Runnable 实例(组件)的执行结果 (即 .invoke() 的返回值)会作为输入 传递给下一个 Runnable 实例。
四、聊天模型核心能力
4.1 定义聊天模型
大语言模型(LLM) 在各种与语言相关的任务(如文本生成、翻译、摘要、问答等)中表现出色。现代 LLM 通常通过聊天模型接口 访问,该接口将消息列表 作为输入,并返回消息作为输出,而不是使用纯文本。
这里需要注意 LLM 与 LangChain 中聊天模型的关系:
| 概念 | 说明 |
|---|---|
| LLM(纯文本补全模型) | 封装的是接受字符串提示、输出字符串补全结果的 API(实际还支持多模态输入) |
| 聊天模型 | 由 LLM 提供支持但专门调整用于对话 :接受聊天消息列表 ,返回一条 AI 消息 |

4.2 通过 API 定义聊天模型
4.2.1 ChatOpenAI(方法一)
ChatOpenAI 定义聊天模型在快速上手 模块中已经涉及。class langchain_openai.chat_models.base.ChatOpenAI 是 LangChain 为 OpenAI 的聊天模型(如 gpt-5.1 、deepseek )提供的具体实现类。其继承了 class langchain_openai.chat_models.base.BaseChatOpenAI,且 BaseChatOpenAI 实现了标准的 Runnable 接口。
常用参数说明:

| 参数 | 说明 |
|---|---|
| max_tokens | 控制模型输出内容的最大长度,以 Token(词元)为单位 |
| timeout | 客户端等待服务器返回完整响应的最长时间(通常以秒为单位) |
| max_retries | 请求因临时性故障失败时,SDK 内部自动重试的最大次数 |
例子:
python
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
temperature=1,
max_tokens=10,
# ......
)
4.2.2 invoke() 调用
介绍方法二之前,需要先了解 Runnable 接口中的 .invoke() 调用。该方法将单个输入 转换为对应的输出 。例如对于聊天模型 ,就是根据用户的问题输入,输出相应的答案。
| 项 | 说明 |
|---|---|
| input | 要传给 Runnable 的输入数据(如消息列表、字符串) |
| config(默认空) | 用于 Runnable 的配置参数 |
| 返回值 | Runnable 处理后的输出(如 AIMessage 或解析后的字符串) |
config参数属性:

4.2.3 init_chat_model(方法二)
ChatOpenAI 用于明确创建 OpenAI 聊天模型的实例。而 init_chat_model() 是一个工厂函数 ,它可以初始化多种支持的聊天模型(如 OpenAI 、Anthropic 、FireworksAI 等),而不仅仅是 OpenAI 的聊天模型。
| 对比 | ChatOpenAI | init_chat_model() |
|---|---|---|
| 定位 | OpenAI 系聊天模型的具体实现类 | 多厂商聊天模型的统一工厂函数 |
| 指定模型 | model="deepseek-chat" |
model="deepseek-chat" + model_provider="deepseek" |
| 优点 | 直接明确 | 一个入口初始化任意厂商模型 |
返回值 :函数返回一个与指定的 model_name 和 model_provider 相对应的 BaseChatModel (如 ChatOpenAI 、ChatAnthropic 等)。


例子:
python
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, SystemMessage
# ========== 1. 初始化聊天模型(init_chat_model 统一入口) ==========
model = init_chat_model(
model="deepseek-chat",
model_provider="deepseek", # 自动使用 langchain-deepseek 的 ChatDeepSeek
temperature=0.7, # 可选:随机性 0~2,越大越发散
max_tokens=2048, # 可选:最大输出 token 数
)
# 不用写 api_key 和 base_url!
# ChatDeepSeek 默认 base_url 就是 api.deepseek.com,Key 自动从环境变量 DEEPSEEK_API_KEY 读
# ========== 2. 最基础用法:直接问一句话 ==========
resp = model.invoke("用一句话介绍你自己")
print("【直接问】", resp.content)
# ========== 3. 多轮对话:传消息列表 ==========
messages = [
SystemMessage(content="你是一个乐于助人的中文助手"),
HumanMessage(content="1+1等于几?"),
]
resp2 = model.invoke(messages)
print("【多轮对话】", resp2.content)
# ========== 方式一:bind() ------ 一次调用临时改参数 ==========
# 改 temperature(低=严谨,高=有创意)
r1 = model.bind(temperature=2).invoke(messages)
print("【bind 改 temperature】", r1.content)
# ========== 方式二:configurable_fields ------ 运行时可切换 ==========
configurable_model_2 = init_chat_model(
model="deepseek-chat",
model_provider="deepseek",
configurable_fields=("model", "model_provider", "temperature"),
)
r2 = configurable_model_2.invoke(messages, config={"configurable": {"temperature": 1}})
print("【configurable 改 temperature】", r2.content)

临时改参数两种方式对比:
| 方式 | 用法 | 特点 |
|---|---|---|
| bind() | model.bind(temperature=2).invoke(...) |
一次调用临时改参数,最直接 |
| configurable_fields | init_chat_model(..., configurable_fields=(...)) |
运行时通过 config 切换,更灵活 |
五、聊天模型调用工具
工具调用 的根本作用是让大语言模型(LLM) 具备与外部世界交互的能力。LLM 本身是一个封闭的知识系统,其能力受限于训练数据 (存在滞后性)和内在的文本生成逻辑 。它无法执行直接计算、查询实时信息、操作数据库或调用任何外部 API。工具调用打破了这层壁垒,其作用具体体现在:
| 作用 | 说明 |
|---|---|
| 扩展能力边界 | 模型可借助工具完成自身无法完成的任务:数学计算、搜索网络、查询数据库等 |
| 保证信息实时性 | 通过搜索/数据库工具获取训练数据中不存在的最新信息,避免回答过时或"一本正经地胡说八道" |
| 处理复杂任务 | 将复杂请求(如"分析上个月消费趋势")分解成多步 ,依次调用不同工具协同完成(协调更体现在 Agent 智能体上) |
| 连接现有系统 | 把企业内部系统、API、数据库封装成工具,让 LLM 成为自然语言驱动的统一接口 |

5.1 创建工具(@tool)
在 LangChain 中,实现了一个 @tool 装饰器来创建工具 ,@tool 装饰器是自定义工具的最简单方法。
python
from langchain_core.tools import tool
@tool(parse_docstring=True)
def multiply(a: int, b: int) -> int:
"""Multiply two integers.
Args:
a: First integer
b: Second integer
"""
return a * b
print(multiply.invoke({"a": 2, "b": 3})) # 输出:6
print(multiply.name) # 输出:multiply
print(multiply.description) # 输出:Multiply two ...省略...b:Second integer
print(multiply.args) # 输出:{'a': {'title': 'A', 'type':'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
可以看出,工具 通过 @tool 加 Python 函数实现,其中:
-
该装饰器默认使用函数名称 作为工具名称。
-
该装饰器将使用函数的文档字符串 作为工具的描述。
因此,函数名 、类型提示 和文档字符串 都是传递给工具 Schema 的一部分,不可缺失。
认识 Schema :Schema(模式/架构)可以理解为一份数据的"蓝图"或"说明书" 。它不包含数据本身,而是定义和描述了数据的结构、格式和规范,确保数据在生产者和消费者之间能被正确理解和使用。
Google 风格的文档字符串 是 Python 文档字符串的一种写作规范 。它并非 Python 语言官方强制要求,而是由 Google 为其内部 Python 项目制定的规范,因其可读性 和简洁性 而在 Python 社区流行。它使用 Args: 、Returns: 等关键字,参数描述简洁明了:
python
def fetch_data(url, retries=3):
"""从给定的URL获取数据。//必填
Args://必填
url (str): 要从中获取数据的URL。
retries (int, optional): 失败时重试的次数。默认为3。
Returns:
dict: 从URL解析的JSON响应。
"""
# ... 函数实现 ...
5.1.1 依赖 Pydantic
若使用 @tool 定义工具时,没有提供文档字符串,则会报错 。此时,在 LangChain 中,可以使用 Pydantic 类,提供运行时数据验证 和类型检查 。通过 Field(description="...") 添加字段描述,LangChain 会自动提取(可以把 Args 部分删除)。
python
# pydantic 数据验证
from pydantic import BaseModel, Field
class AddInput(BaseModel):
"""Add two integers."""
a: int = Field(..., description="First integer")
b: int = Field(..., description="Second integer")
# 定义工具
from langchain_core.tools import tool
@tool(args_schema=AddInput)
def add(a: int, b: int) -> int:
return a + b
5.1.2 依赖 Annotated
在 LangChain 中,可以依赖 Annotated 和文档字符串 来传递给工具 Schema:
python
from langchain_core.tools import tool
from typing_extensions import Annotated
@tool
def add(
a: Annotated[int, ..., "First integer"],
b: Annotated[int, ..., "Second integer"]
) -> int:
"""Add two integers."""
return a + b
两种参数描述方式对比:
| 方式 | 写法 | 适用 |
|---|---|---|
| Pydantic | class XxxInput(BaseModel) + Field(description=...) + args_schema= |
参数多、需要类型校验 |
| Annotated | Annotated[int, ..., "描述"] 直接内联 |
参数少、更简洁 |
5.2 创建工具(StructuredTool)
class langchain_core.tools.structured.StructuredTool 类用于初始化工具 ,其中 from_function 类方法通过给定的函数 来创建并返回一个工具。
from_function 参数说明:
| 参数 | 说明 |
|---|---|
| func | 要设置的工具函数 |
| coroutine | 协程函数 ,要设置的异步工具函数 |
| name | 工具名称 ,默认为函数名称 |
| description | 工具描述 ,默认为函数文档字符串 |
| args_schema | 工具输入参数的 Schema,默认为 None |
| response_format | 工具响应格式,默认为 "content" |
例子(正常):
python
from langchain_core.tools import StructuredTool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
calculator_tool = StructuredTool.from_function(func=multiply)
print(calculator_tool.invoke({"a": 2, "b": 3})) # 输出:6
例子(进阶:args_schema + description):
python
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
def multiply(a: int, b: int) -> int:
return a * b
calculator_tool = StructuredTool.from_function(
func=multiply, # 引用函数
name="Calculator", # 工具名
description="两数相乘", # 介绍功能
args_schema=CalculatorInput, # 介绍参数
)
print(calculator_tool.invoke({"a": 2, "b": 3})) # 输出:6
print(calculator_tool.name) # 输出:Calculator
print(calculator_tool.description) # 输出:两数相乘
print(calculator_tool.args) # 输出:{'a': {'description': 'first number', ...}, ...}
5.2.1 response_format
在 LangChain 中,如果希望工具区分消息内容(content) 和其他工件(artifact) ,可以通过将 response_format 设置为 "content_and_artifact" 来实现。这样,大模型 可以读取 content ,而一些用于构造 content 的原始数据 可以保存为 artifact ,便于后续记录、分析等步骤使用。artifact 通常使用字典(Dict) 或列表(List) 来保存。
| 字段 | 说明 | 例子 |
|---|---|---|
| content | 模型可读的结构化、简洁文本 | "今天北京晴,气温在25°C到32°C之间。建议穿短袖衣物。" |
| artifact | 构造 content 的原始数据(如 API 完整 JSON 响应) | 搜索结果条目的标题、链接、摘要、排名等元数据 |
python
# Artifact 的⽰例结构
{
'results': [
{
'title': '北京天⽓预报 - 中国天⽓⽹',
'link': 'https://weather.com.cn/...',
'snippet': '北京今天⽩天晴,最⾼⽓温32°C,夜间晴,最低⽓温25°C...'
},
{
'title': '北京实时天⽓ - Weather.com',
'link': 'https://www.weather.com/...',
'snippet': 'Bejing, China Weather. Mostly sunny. High 32C...'
}
# ... 更多结果
],
'search_parameters': { ... },
'search_information': { ... }
}
在定义工具 时,需要指定 response_format="content_and_artifact" 参数,并确保返回一个元组 (content, artifact):
python
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
from typing import List, Tuple
class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
def multiply(a: int, b: int) -> Tuple[str, List[int]]:
nums = [a, b]
content = f"{nums}相乘的结果是{a * b}"
return content, nums
calculator_tool = StructuredTool.from_function(
func=multiply,
name="Calculator",
description="两数相乘",
args_schema=CalculatorInput,
response_format="content_and_artifact"
)
print(calculator_tool.invoke({"a": 2, "b": 3})) # 输出:('...相乘的结果是6', [2, 3])
为什么这样设计:
-
LLM 主要理解文本 ,因此工具的主要输出 content 必须是结构良好、简洁的文本,便于模型推理和生成后续指令。
-
在链(Chain) 中,工具调用之后 的其他组件或函数,可能需要原始且结构化的数据 (即 artifact )来执行特定操作。这些数据可能庞大且非文本,不适合直接塞给模型。
-
artifact 是为链中后续组件或函数 使用的,不直接被大模型使用。
5.3 绑定工具
为了实际将这些工具 绑定到聊天模型 ,可以使用聊天模型的 .bind_tools() 方法:
python
# 绑定工具,返回一个 Runnable 实例
tools = [add, multiply]
model_with_tools = model.bind_tools(tools)
参数说明:
| 参数 | 说明 |
|---|---|
| tools | 绑定到聊天模型的工具定义列表 ,支持:字典 、pydantic.BaseModel 类、Python 函数 、BaseTool (如 @tool 创建的类) |
| tool_choice(默认空) | 要求模型调用哪个工具,见下表 |
| strict(默认空) | True 保证模型输出与 JSON Schema 完全匹配 ;False 不验证;None 不传递该参数 |
| parallel_tool_calls(默认 None) | 允许并行工具调用 ;设为 False 可禁用 |
| kwargs(Any) | 附加参数,直接传递给 bind() |
tool_choice 取值:
| 值 | 行为 |
|---|---|
'<<tool_name>>' |
调用指定的工具 |
'auto' |
自动选择工具(包括无工具) |
'none' |
不调用工具 |
'any' / 'required' / True |
强制调用至少一个工具 |
False / None |
无效果,使用模型默认行为 |
返回值 :返回一个 Runnable 实例。
-
该实例支持多种格式输入 :原始提示 PromptValue 、字符串 (如
"上海天气如何?")、消息或消息列表。 -
该实例的输出 :一个 AIMessage 对象,其中包含工具调用信息。
5.3.1 工具调用
python
import os
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from typing_extensions import Annotated
# 定义大模型
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com"
)
@tool
def add(a: int, b: int) -> int:
"""Add two integers.
Args:
a: First integer
b: Second integer
"""
return a + b
@tool
def multiply(
a: Annotated[int, ..., "First integer"],
b: Annotated[int, ..., "Second integer"]
) -> int:
"""Multiply two integers."""
return a * b
# 绑定工具
tools = [add, multiply]
model_with_tools = model.bind_tools(tools)
# 调用工具
result = model_with_tools.invoke("9加6等于多少?")
print(result)

输出说明 :AIMessage 来自 AI 的消息,从聊天模型返回,作为对提示(输入) 的响应。其中:
-
content :消息的内容。
-
additional_kwargs :与消息关联的其他有效载荷数据 ,可能包含模型提供商编码的工具调用。
-
response_metadata :响应元数据,例如响应头、logprobs、令牌计数、模型名称等。
注意 :工具调用的关键原则是模型根据输入的相关性决定何时使用工具 ,模型并不总是需要调用工具。例如,给定一个不相关的输入,模型不会调用该工具:
python
result = model_with_tools.invoke("hello world!")
print(result)

5.3.2 强制模型调用工具
我们也可以强制模型调用工具 ,在绑定工具时设置 tool_choice="any" ,表示强制调用至少一个工具:
cpp
model_with_tools = model.bind_tools(tools, tool_choice="any")
result = model_with_tools.invoke("hello world!")
print(result)

5.3.3 工具属性
现在我们知道,输出结果是一个 AIMessage 。但是,如果调用了工具 ,则 result 将具有一个 tool_calls 属性。此属性包括执行该工具所需的一切,包括工具名称 和输入参数:
python
# 绑定工具
tools = [add, multiply]
model_with_tools = model.bind_tools(tools, tool_choice="any")
# 调用工具
result = model_with_tools.invoke("9加6等于多少?")
print(result.tool_calls)

5.3.4 将工具输出传递给聊天模型
到这里可以发现,我们仅仅只是成功调用了工具 ,但聊天模型 并没有返回我们真正需要的答案。此时需要:
-
将工具输出传递给聊天模型 ,包括 HumanMessage 、AIMessage(工具调用)
-
聊天模型 根据以上消息列表 输入,将最终结果以 AIMessage 返回。
为什么要发 ToolMessage 呢? 之前讲过,聊天模型通常不是 接受单个字符串作为输入,而是接受聊天消息(XxxMessage)列表 。因此我们需要将工具的返回结果构造成 ToolMessage ,再传输给聊天模型。方便的是 ,如果我们使用 @tool 装饰器创建的工具 ,调用 tool.invoke(tool_calls) 将自动返回一个 ToolMessage。
python
import os
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
# 1. 初始化模型
model = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"), # Key 从环境变量读
base_url="https://api.deepseek.com", # DeepSeek 接口地址,必须指定
)
# 2. 定义工具
@tool
def add(a: int, b: int) -> int:
"""Add two integers.""" # docstring 第一行 = 工具描述
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b
# 3. 绑定工具
model_with_tools = model.bind_tools([add, multiply])
# 4. 第一轮:模型决定是否调用工具
messages = [HumanMessage("9乘6等于多少?5加3等于多少?")] # 对话历史
ai_message = model_with_tools.invoke(messages) # 模型返回:直接回答 或 工具申请单
messages.append(ai_message) # 回复加入历史
# 5. 执行模型请求的工具
for tool_call in ai_message.tool_calls:
tool_message = {"add": add, "multiply": multiply}[tool_call["name"]].invoke(tool_call)
messages.append(tool_message) # 工具结果回传
# 6. 模型给出最终答案
result = model.invoke(messages)
print(result.content)
工具调用完整流程总结:
| 步骤 | 动作 |
|---|---|
| ① 绑定工具 | model.bind_tools([add, multiply]) |
| ② 模型决策 | 模型返回直接回答,或带有 tool_calls 的 AIMessage(工具申请单) |
| ③ 执行工具 | 按 tool_calls 调用对应工具,得到 ToolMessage |
| ④ 回传结果 | 把 ToolMessage 追加进消息列表 |
| ⑤ 模型作答 | 模型根据完整消息列表给出最终答案 |
六、复盘(附答案)
💡 思考题
-
LangChain 中 SystemMessage、HumanMessage、AIMessage、ToolMessage 分别代表什么角色?为什么工具结果要构造成 ToolMessage 回传?
-
Runnable 接口是什么?它解决了原生大模型的什么问题?
-
LCEL 的
chain = model | parser是如何工作的?chain.invoke(messages)发生了什么? -
创建工具有哪几种方式?各有什么特点?
-
工具调用的完整流程分几步?为什么说"模型并不总是需要调用工具"?
📝 答案
-
SystemMessage (系统角色):消息序列第一条,启动 AI 行为 ;HumanMessage (用户角色):用户传给模型的输入;AIMessage (AI 角色):聊天模型返回的回答(含工具调用信息
tool_calls);ToolMessage (工具角色):工具执行后返回的结果。因为聊天模型接受的是"消息列表"而不是单个字符串,所以必须把工具结果构造成 ToolMessage 再回传给模型,模型才能据此给出最终答案。 -
Runnable 是 LangChain 组件的统一标准接口 ,支持调用(invoke)、批处理(batch)、流式(stream)、检查(inspect)、组合(compose)五种能力。它解决了"切换大模型困难"的问题------模型、解析器、链都实现同一标准接口,换模型不会引发连锁反应。
-
chain = model | parser用 LCEL 构建了一条 RunnableSequence 。chain.invoke(messages)时:上一个组件的输出作为下一个组件的输入------模型先对消息生成 AIMessage,解析器再把结果解析为字符串,一次性得到最终结果。 -
四种方式:@tool 装饰器 (最简单,函数名=工具名,文档字符串=描述);@tool + Pydantic (用
args_schema提供参数描述与类型校验,适合没文档字符串时);@tool + Annotated (在参数上内联描述,更简洁);StructuredTool.from_function(面向对象式,可显式指定 name/description/args_schema/response_format)。 -
五步:绑定工具 (bind_tools)→ 模型决策 (返回直接回答或带 tool_calls 的 AIMessage)→ 执行工具 (按 tool_calls 调用,得到 ToolMessage)→ 回传结果 (追加进消息列表)→ 模型作答 。因为工具调用应由模型"按输入相关性"自主决定------不相关的输入(如"hello world")就不该调用工具,强制调用(tool_choice="any")只是可选手段。
🎯 闭幕

从"定义大模型"到"Runnable 与 LCEL",从"聊天模型"到"工具调用"------这一篇帮你把 LangChain 的入门实战走了一遍。
如果本文对你有帮助,欢迎:
👍 点赞 | ⭐ 收藏 | 👤 关注作者 | 💬 留言交流你的疑问或补充
你的每一次互动都是我继续更新的动力,我们下一篇见!🚀