1. LangChain 生态分包介绍

LangChain 生态系统包含不同的包,用来准确选择要安装的功能。如下图所示:

在这里插入图片描述

  • 底层公共依赖:langchain-core
  • langchain 依赖 langchain-core
  • langchain-community 依赖 langchain
  • langgraph 直接依赖 langchain-core
  • integrations 集成包(langchain-openai / langchain-postgres / langchain-anthropic 等)全部直接依赖 langchain-core

主 langchain 包

这个包是使用 LangChain 的起点,安装方式:

pip install langchain

langchain-core 包

除了 langsmith SDK 之外,LangChain 生态系统中的所有包都依赖于 langchain-core,包含其它包使用的基类和抽象,以及 LangChain LCEL(表达式语言)。

它由 langchain 包自动安装,不需要显式安装该包。但是,如果使用的功能仅在该依赖项的特定版本本中可用,则可以选择这样做。如果这样做,则应确保已安装或固定的版本与使用的任何其他集成包兼容。

pip install langchain-core

Integrations 集成包

LangChain 的大部分价值来自于将各种能力进行集成,如各类模型集成(如 OpenAI 和 Anthropic)、各类组件集成(如数据存储、工具等)等。LangChain 中集成好的包见这里。

对于所需依赖项,需要单独安装。例如要使用 OpenAI,可以运行:

pip install langchain-openai

langchain-community 包

简单来说,任何尚未拆分到自己的包中的集成,都存在于 langchain-community 包中。安装方式:

pip install langchain-community

langgraph 包

langgraph 是一个库,用于使用 LLM 构建有状态的应用程序。它与 LangChain 顺利集成。安装方式:

pip install langgraph

LangSmith SDK

LangSmith SDK 由 LangChain 自动安装。但它不依赖于 langchain-core,如果需要,可以独立安装和使用,安装方式:

pip install langsmith

2. 快速接入OpenAI实操

2.1 申请API key并配置环境变量

1. 申请API key

以OpenAI为例,官网地址
如果没有账号先注册账号,登录成功后,出现settings图标,点击settings,选择API keys配置页面:

  1. 左侧菜单栏点击 API keys
  2. 右上角齿轮 settings 图标
  3. 点击 + Create new secret key

点击“Create new secret key”按钮,新增API key:

  1. 输入key名称(自定义)
  2. Project选择默认项目
  3. Permissions权限默认All,点击Create secret key
  4. 创建完成后页面展示生成的密钥

将API Key在自己本地保存好,后续接入ChatGPT时需要使用。

注意:密钥只展示一次,丢失无法找回,务必复制保存。

对于不方便进行充值的友友,作者这里推荐一个还不错的中转站,PackyCode可以参考文档,进行apikey的配置;

2. 配置环境变量

Windows系统环境变量配置步骤:

  1. 系统变量区域点击【新建】
  2. 变量名输入:OPENAI_API_KEY
  3. 变量值粘贴申请好的完整API Key
  4. 点击【确定】保存编辑窗口
  5. 再次点击【确定】关闭环境变量总窗口

配置完成后需要重启终端/IDE才能读取到新环境变量

2.2 定义大模型

1. 安装OpenAI包

pip install -U langchain-openai

2. 定义大模型

代码:

# 定义大模型
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 1.定义OpenAI模型(使用中转站)
model = ChatOpenAI(
    model="gpt-5.4",
    temperature=0,
    api_key="sk-XXX",       # 替换成你的实际 key
    base_url = "https://www.packyapi.com/v1",  # 替换成你的中转站地址,或者不需要
)

2.3 定义消息列表

代码:

# 2.定义消息列表
messages = [
    SystemMessage(content="你是一个有帮助的助手。"), # 这是系统消息,告诉模型它的角色和行为
    HumanMessage(content="你是什么模型?请用一句话回答。"), # 这是人类消息,向模型提出问题
]

参数说明:

  • SystemMessage:表示系统角色消息,系统消息通常作为输入消息序列中的第一条传入,是用来启动AI行为的消息。
  • HumanMessage:表示用户角色消息,是来自用户、从用户传递到模型的消息。

2.4 调用大模型

model 是LangChain Runnable(可运行)接口的实例,这意味着model提供了一个标准接口供我们与之交互。要简单地调用模型,我们可以将消息列表传递给 .invoke 方法。

使用 .invoke 方法进行大模型调用,代码:

result = model.invoke(messages)
print(result)

输出结果(调试可以看见 result 类型为 AIMessage):

content='我是由 OpenAI 提供的 AI 助手。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 56, 'total_tokens': 88, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'gpt-5.4', 'system_fingerprint': None, 'id': 'resp_0e57bb03572b0219016a4c898b64188198b8557c0ffa53364d', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--019f3af9-43fb-7c21-8ea0-21b13b4f39a0-0' usage_metadata={'input_tokens': 56, 'output_tokens': 32, 'total_tokens': 88, 'input_token_details': {}, 'output_token_details': {}}
我是由 OpenAI 提供的 AI 助手。

在这里插入图片描述

输出说明:

  • AIMessage:来自AI的消息。从聊天模型返回,作为对提示(输入)的响应。
    • content:消息的内容。
    • additional_kwargs:与消息关联的其他有效负载数据。对于来自AI的消息,可能包括模型提供程序编码的工具调用。
    • response_metadata:响应元数据。例如:响应标头、logprobs、令牌计数、模型名称。
      • 侧重于“响应”本身的信息,比如这次请求的ID、使用的模型版本、以及服务商返回的所有原始元数据。它主要用于调试、日志记录和获取请求的上下文信息。
    • usage_metadata:消耗的量化使用元数据,例如令牌消耗计数。
      • 侧重于“资源消耗”的量化信息,即这次请求消耗了多少Token。它主要用于成本计算、监控和预算控制。

2.5 输出解析

若只想输出聊天模型返回的字符串,可以使用 StrOutputParser 输出解析器组件,将大模型输出结果解析为最纯粹的字符串。代码:

# from langchain字符串输出解析器
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
print(parser.invoke(result))

输出结果:

你好!

2.6 链式执行

通过上述步骤,无论是调用大模型,还是输出解析,每次都调用了一个 invoke() 方法,最终才会得到我们想要的结果。

对于LangChain,它给我们提供了链式执行的能力,即我们只需要定义各个“组件”,将它们“链起来”,一次性执行即可得到最终效果。

核心代码如下:

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 1.定义OpenAI模型(使用中转站)
model = ChatOpenAI(
    model="gpt-5.4",
    temperature=0,
    api_key="sk-Wxxx",       # 替换成你的实际 key
    base_url = "https://www.packyapi.com/v1",  # 替换成你的中转站地址
)

# 2.定义消息列表
messages = [
    SystemMessage(content="你是一个有帮助的助手。"), # 这是系统消息,告诉模型它的角色和行为
    HumanMessage(content="你是什么模型?请用一句话回答。"), # 这是人类消息,向模型提出问题
]

# 3.调用大模型
result = model.invoke(messages)  # 调用模型,传入消息列表
# 返回的其实是一个包含模型输出的对象,通常包括 content、usage 等信息,其中AIMessage表示ai返回的消息
print(result) # 输出模型的回答

# 4.定义输出解析器
parser=StrOutputParser() # 创建一个字符串输出解析器实例
print(parser.invoke(result)) # 解析模型的输出内容,并打印结果

# 5.定义链
chain=model | parser
print(chain.invoke(messages)) # 直接调用链,传入消息列表,返回解析后的结果

输出结果:

你好!

3. 引出LangChain相关概念

3.1 Runnable 接口

Runnable 接口是使用LangChain Components(组件)的基础。

概念:

  • Components(组件):用来帮助我们构建应用程序时,提供了一系列的核心构建块,例如语言模型、输出解析器、检索器、编译的LangGraph图等。
  • Runnable 定义了一个标准接口,允许 Runnable 组件:
    1. invoke(调用):单个输入转换为输出。
    2. batch(批处理):多个输入被有效地转换为输出。
    3. stream(流式传输):输出在生成时进行流式传输。
    4. inspect(检查):可以访问有关 Runnable 的输入、输出和配置的原理图信息。
    5. composed(组合):可以组合多个 Runnable,以使用 LCEL 协同工作以创建复杂的管道。

因此,刚才定义的语言模型(model)、输出解析器(StrOutputParser)都是 Runnable 接口的实例。都使用了 invoke(调用)的能力,
也就是说不管是什么组件,执行逻辑统一都使用相同的接口,也就是invoke等,消除了组件之间的差异:

# 语言模型(model)
result = model.invoke(messages)
# 输出解析器(StrOutputParser)
parser = StrOutputParser()
parser.invoke(result)

3.2 LangChain Expression Language(LCEL)

LangChain Expression Language:采用声明性方法,从现有Runnable对象构建新的LangChain对象

通过LCEL构建出的新的Runnable对象,被称为 RunnableSequence,表示可运行序列。RunnableSequence 就是一种链,通过调试步骤就能发现,chain的类型就是 RunnableSequenceRunnableSequence 也是 Runnable 接口的实例,它实现了完整的Runnable接口,因此它可以与任何其他Runnable以相同的方法使用。

chain = model | parser
# chain 是 Runnable 接口实例,允许invoke调用

LCEL其实是一种编排解决方案,它使LangChain能够以优化的方式处理链的运行时执行。任何两个Runnable实例都可以“链”在一起成序列。上一个可Runnable对象的 .invoke() 调用的输出作为输入递给下一个可运行对象。方法就是使用 |(管道运算符):

chain = model | parser

它通过两个Runnable对象去创建一个RunnableSequence。在本质上LangChain重载了|运算符,使用|运算符就相当于:

from langchain_core.runnables import RunnableSequence
chain = RunnableSequence(first=model, last=parser)

除此之外,可以使用 .pipe 方法代替。这也相当于 | 运算符:

chain = model.pipe(parser)

本人认为pipe或者RunnableSequence等方法没有直接使用|符号直观,推荐在工程项目中使用|

4. 聊天模型核心能力

4.1 定义聊天模型

大语言模型(LLM)在各种与语言相关的任务(例如文本生成、翻译、摘要、问答等)中表现出色。现代LLM通常通过聊天模型接口访问,该接口将消息列表作为输入,并返回消息作为输出,而不是使用纯文本。

这里需要注意 LLM 与 LangChain 中聊天模型 的关系:

  • 在 LangChain 的官方文档中,认为 LLM 大多数是纯文本补全模型。这些纯文本模型封装的API接受一个字符串提示作为输入,并输出一个字符串补全结果(实际上LLM还包括多模态输入)。
  • LangChain 中的聊天模型通常由 LLM 提供支持,但经过专门调整以用于对话。关键在于不是接受单个字符串作为输入,而是接受聊天消息列表,并返回一条AI消息作为输出。

流程图说明:

  1. LLM:输入单字符串 → 输出单字符串
  2. 聊天模型:输入多条Message列表 → 输出AIMessage

4.1.1 通过 API 定义聊天模型

方式1:ChatOpenAI

class langchain_openai.chat_models.base.ChatOpenAI 是LangChain为OpenAI的聊天模型(如 gpt-5,gpt-5-mini)提供的具体实现类。其继承了 class langchain_openai.chat_models.base.BaseChatOpenAI,且 BaseChatOpenAI 实现了标准的 Runnable 接口。

ChatOpenAI 常用初始化参数说明

参数名 参数描述
model 要使用的OpenAI模型的名称
temperature 采样温度,温度值越高,AI回答越离谱;温度越低,回答越靠谱。
max_tokens 要生成的最大令牌数
timeout 请求超时时间
max_retries 最大重试次数
openai_api_key / api_key OpenAI API密钥。如果未传入,将从环境变量中读取 OPENAI_API_KEY
base_url API请求的基本URL。一般用来指定中转站
organization OpenAI组织ID。如果未传入,将从 env var OPENAI_ORG_ID 中读取。
…… 其他扩展参数
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="gpt-5-mini",
    temperature=0,
    max_tokens=None,
    timeout=None,
    max_retries=2,
    # api_key="...",
    # base_url="...",
    # organization="...",
    # other params...
)

若使用其它与OpenAI兼容的大模型,例如 DeepSeek,则可以使用以下定义方式:

import os
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')

from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    base_url="https://api.deepseek.com/v1",
    openai_api_key=OPENAI_API_KEY,
    model="deepseek-chat",
    # ...
)

参数说明:

  • base_url:出于与OpenAI兼容考虑,要将 base_url 设置为 https://api.deepseek.com/v1 来使用,注意此处 v1 与模型版本无关
  • openai_api_key:需要单独申请 DeepSeek 的 API Key,然后重新进行环境变量配置。DeepSeek API Key 申请地址

invoke() 调用

Runnable 接口中的 .invoke() 调用:该方法是将单个输入转换为对应的输出。例如对于聊天模型来说,就是根据用户的问题输入,输出相应的答案。

invoke() 方法定义

abstractmethod invoke(
    input,
    config: RunnableConfig | None = None,
    **kwargs: Any,
) -> Any

请求参数:

  • input:输入一个 Runnable 实例
  • config(默认空):用于 Runnable 的配置。

返回值:
返回一个 Runnable 实例

from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage

# 1. 创建可配置聊天模型
configurable_llm = init_chat_model(
    temperature=0,
    # 允许运行时修改这些字段
    configurable_fields=["model", "model_provider", "temperature"],
    config_prefix="llm" #指定修改的前缀
)

# 2. invoke 时传入 config
result = configurable_llm.invoke(
    input=[HumanMessage(content="你好,介绍下自己")], #传入的是一个消息列表
    config={
        "run_name": "动态DeepSeek对话",  # 日志追踪名称
        "metadata": {"biz": "demo", "user_id": "1001"},  # 自定义业务元数据
        "configurable": {
            "llm_model": "deepseek-chat",
            "llm_model_provider": "deepseek",
            "llm_temperature": 0.7
        }
    }
)

class langchain_core.runnables.config.RunnableConfig 常用参数说明

参数名 参数描述
configurable 通过 configurable_fields() 在此 Runnable 或子 Runnable 上配置的属性的运行时值。
run_id 针对调用运行的跟踪器的唯一标识符。如果未提供,将生成新的UUID。
run_name 此调用的跟踪器运行的名称。默认为类的名称。
metadata 此次调用和任何子调用的元数据。键是字符串,值是JSON。类型:dict[str, Any]
…… 其他扩展参数
方式2:init_chat_model

ChatOpenAI 用于明确创建 OpenAI 聊天模型的实例。而 init_chat_model() 是一个工具函数,它可以初始化多种支持的聊天模型(如 OpenAI、Anthropic、FireworksAI 等),不仅仅是 OpenAI 的聊天模型。

init_chat_model() 函数定义

langchain.chat_models.base.init_chat_model(
    *,
    model: str,
    model_provider: str | None = None,
    configurable_fields: Literal[None] | None = None,
    config_prefix: str | None = None,
    **kwargs: Any,
) -> BaseChatModel

init_chat_model() 常用参数说明

参数名 参数描述
model 要使用的模型的名称
model_provider 模型提供方。支持的 model_provider 值和相应的集成包有:
openai -> langchain-openai
anthropic -> langchain-anthropic
google_genai -> langchain-google-genai
ollama -> langchain-ollama
deepseek -> langchain-deepseek
• ……
如果未指定,将尝试从模型推断 model provider。
configurable_fields 设置哪些模型参数是可配置的。若配置为:
None: 没有可配置的字段。
'any': 所有字段都是可配置的,类似 api_keybase_url 等可以在运行时更改。
Union[List[str],Tuple[str,...]]: 指定的字段是可配置的。
config_prefix 配置为非空字符串,则模型将在运行时通过查找 config["configurable"][f"{config_prefix}_{param}"] 字段设置配置项。
配置为 '' 字符串,那么配置项可以通过 config["configurable"][param]
temperature 采样温度,温度值越高,AI回答天马行空;温度越低,回答越保守靠谱。
max_tokens 要生成的最大令牌数
timeout 请求超时时间
max_retries 最大重试次数
openai_api_key / api_key OpenAI API密钥。如果未传入,将从环境变量中读取
base_url API请求的基本URL。
…… 其他扩展参数

init_chat_model() 返回值说明
函数返回一个与指定的 model_namemodel_provider 相对应的 BaseChatModel(如 ChatOpenAIChatAnthropic 等)。注意要是模型可配置,则返回一个聊天模型模拟器,该模拟器在传入配置后,于运行时才会初始化底层模型。

示例1:基本用法
使用不同的模型提供方,需要安装为其各自包,与设置各自的API Key环境变量!例如:

  • OpenAI 环境变量配置为:OPENAI_API_KEY= "your openai api key"
    • 安装命令:pip install -U langchain-openai
  • Anthropic 环境变量配置为:ANTHROPIC_API_KEY= "your anthropic api key"
    • 安装命令:pip install -U langchain-anthropic
  • DeepSeek 环境变量配置为:DEEPSEEK_API_KEY= "your deepseek api key"
    • 安装命令:pip install -U langchain-deepseek
  • Google VertexAI 环境变量配置为:GOOGLE_API_KEY= "your google api key"
    • 安装命令:pip install -U langchain-google-vertexai
  • 更多见官方文档
from langchain.chat_models import init_chat_model

# 返回 langchain_openai.ChatOpenAI 实例
gpt_model = init_chat_model("gpt-5-mini", model_provider="openai", temperature=0)
# 返回 langchain_deepseek.ChatDeepSeek 实例
deepseek_model = init_chat_model("deepseek-chat", model_provider="deepseek", temperature=0)

# 由于所有模型集成都实现了ChatModel接口,因此可以以相同的方式使用它们。
print("gpt-5-mini: " + gpt_model.invoke("what's your name?").content + "\n")
print("deepseek-chat: " + deepseek_model.invoke("what's your name?").content + "\n")

输出:

gpt-5-mini: I'm called ChatGPT. How can I assist you today?

deepseek-chat: I'm DeepSeek Chat! 😊 You can call me DeepSeek or just Chat if you'd like. I'm here to help with anything you need-ask me anything!

示例2:创建可配置模型

# 可配置模型模拟器
configurable_model_1 = init_chat_model(
    temperature=0
)
# 动态修改配置,初始化模型并调用
configurable_result_1 = configurable_model_1.invoke(
    "what's your name?",
    config={
        "configurable": {"model": "gpt-5-mini"}
    }
)
print("configurable1: " + configurable_result_1.content + "\n")

输出:

configurable1: I'm called ChatGPT. How can I assist you today?

示例3:具有默认值的可配置模型

# 可配置模型模拟器2
configurable_model_2 = init_chat_model(
    model="gpt-5-mini",
    temperature=0,
    configurable_fields=("model", "model_provider", "temperature", "max_tokens"),
    config_prefix="first",
)

# 动态修改配置,初始化模型并调用
configurable_result_2 = configurable_model_2.invoke(
    "what's your name?",
    config={
        "configurable": {
            "first_model": "deepseek-chat",
            "first_temperature": 0.5,
            "first_max_tokens": 100,
        }
    },
)
print("configurable2: " + configurable_result_2.content + "\n")

输出:

configurable2: My name is DeepSeek Chat! 😊 I'm here to help you with any questions or topics you're curious about. How can I assist you today?

4.1.2 通过本地部署的 LLM 定义聊天模型

ChatOllama

若想使用 ChatOllama,需要先安装 Ollama 包:

pip install -U langchain_ollama

class langchain_ollama.chat_models.base.ChatOllama 是LangChain为通过Ollama部署的聊天模型提供的具体实现类。ChatOllama 同样也实现了标准的 Runnable 接口。

ChatOllama 常用初始化参数说明

参数名 参数描述
model 要使用的Ollama模型的名称
temperature 采样温度,温度值越高,AI回答天马行空;温度越低,回答越保守靠谱。
timeout 请求超时时间
base_url API请求的基本URL。
num_ctx 设置用于生成下一个令牌的上下文窗口的大小。(默认值:2048)
num_gpu 要使用的GPU数量。在macOS上,默认为1表示启用金属支持,默认为0表示禁用。
…… 其他扩展参数

示例代码

from langchain_ollama import ChatOllama

ollama_model = ChatOllama(model="deepseek-r1:70b", base_url='http://127.0.0.1:11434')
result = ollama_model.invoke("what's your name?")
print(result)

输出:

content="\n\n\n\nGreetings! I'm DeepSeek-R1, an artificial intelligence assistant created by DeepSeek. I'm at your service and would be delighted to assist you with any inquiries or tasks you may have." additional_kwargs={} response_metadata={'model': 'deepseek-r1:70b', 'created_at': '2025-08-20T06:10:54.974263Z', 'done': True, 'done_reason': 'stop', 'total_duration': 9158375400, 'load_duration': 1032569000, 'prompt_eval_count': 8, 'prompt_eval_duration': 1303997800, 'eval_count': 44, 'eval_duration': 7739543700, 'model_name': 'deepseek-r1:70b'} id='run--c62716da-3c5c-4ddf-8e76-da27dc9291a7-0' usage_metadata={'input_tokens': 8, 'output_tokens': 44, 'total_tokens': 52}

4.2 聊天模型调用工具

工具调用根本作用是让大语言模型(LLM)具备与外部世界交互的能力。
LLM本身是一个封闭的知识系统,其能力受限于其训练数据(存在滞后性)和内在的文本生成逻辑。它无法执行直接计算、查询实时信息、操作数据库或调用任何外部API。工具调用打破了这层壁垒,其作用具体体现在:

  1. 扩展能力边界:模型可以借助工具完成它自身无法完成的任务,如执行数学计算、搜索网络、查询数据库等。
  2. 保证信息实时性:通过调用搜索工具或数据库查询工具,LLM可以获取最新的、训练数据中不存在的信息,避免回答过时或“一本正经地胡说八道”。
  3. 处理复杂任务:将一个复杂的用户请求(如“分析我上个月的消费趋势”)分解成多个步骤,并依次调用不同的工具(如“从数据库获取数据” -> “用Python进行数据分析” -> “生成图表”)来协同完成。协调这件事更体现在Agent智能体上。
  4. 连接现有系统:可以将企业内部已有的系统、API和数据库封装成工具,让LLM成为一个用自然语言驱动的统一接口,极大地提升了自动化和集成能力。

4.2.1 创建工具

使用 @tool 装饰器创建工具

在LangChain中,实现了一个@tool装饰器来创建工具,@tool装饰器是自定义工具的最简单方法。如下所示:

from langchain_core.tools import tool

@tool
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 integers. ...省略...
print(multiply.args)
# 输出: {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}

可以看出,工具通过@tool加Python函数实现,其中:

  • 该装饰器默认使用函数名称作为工具名称
  • 该装饰器将使用函数的文档字符串作为工具的描述

因此,函数名、类型提示和文档字符串都是传递给工具 Schema 的一部分,不可缺失。定义好的描述是使模型良好运行的重要部分。

工具 Schema的具体定义

示例1

{
  "name": "张小红",
  "birthDay": "1972年2月22日",
  "address": "陕西省西安市雁塔区"
}

示例2

{
  "surname": "王",
  "givenName": "刚",
  "birthDate": "1972-02-22",
  "address": {
    "district": "萧山区",
    "city": "杭州市",
    "province": "浙江省",
    "country": "中国"
  }
}

这两种表述同样有效,尽管示例2显然比示例1更正式。记录的设计在很大程度上取决于其在应用程序中的预期用途,因此这里没有正确或错误的答案。重要的是要确切地知道该如何组织记录。例如,当应用程序需要“一个人的JSON记录”时,这就是要确切地知道该如何组织记录。你可能见过这种可视化配置方式,实际上是在构造JSON Schema。

转换成编码方式,则为以下内容(此JSON Schema片段描述了上述第二个示例的结构):

{
  "type": "object",
  "properties": {
    "surname": {"type": "string"},
    "given_name": {"type": "string"},
    "birthday": {"type": "string", "format": "date"},
    "address": {
      "type": "object",
      "properties": {
        "district": {"type": "string"},
        "city": {"type": "string"},
        "province": {"type": "string"},
        "country": {"type": "string"}
      }
    }
  }
}

若用此 JSON Schema “验证”示例1,那么示例1是不符合当前 JSON Schema 的,但是示例2可以“验证”通过,JSON Schema 是数据本身,只是一种描述其他数据结构的声明格式。简明扼要地描述数据的表面结构,并根据数据自动验证数据很容易。由于 JSON Schema 不能包含任意代码,因此无法表达数据元素之间的关系存在某些约束。

总结:Schema 就是描述其他数据结构的声明格式,用于自动验证数据而存在。

对于工具schema,它将从函数名、类型提示和文档字符串中获取相关属性,以此来声明一个工具,包括其名称、描述、输入参数、输出类型等等这些属性可以帮助AI更好地知道如何去调用模型以及具体调用哪一个模型。这里需要说明的是,若是简单定义工具,如上述示例,工具schema需要解析 Google 风格的文档字符串去获取参数描述。

Google 风格是 Python 文档字符串的一种写作规范。它并非 Python 语言官方强制要求,而是由 Google 为其内部 Python 项目制定的规范,后来因为其极高的可读性和简洁性而在整个 Python 社区中变得非常流行。它使用 Args:Returns: 等关键字,参数描述简洁明了,如下所示:

def fetch_data(url, retries=3):
    """从给定的URL获取数据。

    Args:
        url (str): 要从中获取数据的URL。
        retries (int, optional): 失败时重试的次数。默认为3。

    Returns:
        dict: 从URL解析的JSON响应。
    """
    # ... 函数实现 ...

除了 @tool 装饰器+文档字符串这种方式,还有其他方式可以让工具 schema 获取相关工具声明需要的内容:

模式1:依赖 Pydantic 类

若使用 @tool 定义工具时,没有提供文档字符串,则会报错:

from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    return a * b

点击运行,报错:ValueError: Function must have a docstring if description not provided.

此时,在 LangChain 中,可以使用 Pydantic 类,提供运行时数据验证和类型检查。通过 Field(description="...") 添加字段描述,LangChain 会自动提取。
注意,除非提供默认值,否则所有字段都是 required。如下所示:

from pydantic import BaseModel, Field

class AddInput(BaseModel):
    """Add two integers."""
    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")

class MultiplyInput(BaseModel):
    """Multiply two integers."""
    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")

完整代码如下所示:

# pydantic 数据验证
from pydantic import BaseModel, Field

class AddInput(BaseModel):
    """Add two integers."""
    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")

class MultiplyInput(BaseModel):
    """Multiply two integers."""
    a: int = Field(..., description="First integer")
    b: int = Field(..., description="Second integer")

# 定义工具
from langchain_core.tools import tool

@tool(args_schema=AddInput) # 这代表输入参数的具体含义去addinput类中找
def add(a: int, b: int) -> int:
    # 未提供描述
    return a + b

@tool(args_schema=MultiplyInput)
def multiply(a: int, b: int) -> int:
    # 未提供描述
    return a * b

注意代码中 @toolargs_schema 参数,它表示工具函数在未提供描述、文档字符串等需要传递给工具 Schema 的内容时,依赖 Pydantic 类使用 args_schema 参数,定义并提供工具输入参数的schema。默认为None,且将来运行时会进行数据验证。

模式2:依赖 Annotated

在 LangChain 中,可以依赖 Annotated 和文档字符串传递给工具 Schema。如下所示:

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

@tool
def multiply(
    a: Annotated[int, ..., "First integer"],
    b: Annotated[int, ..., "Second integer"],
) -> int:
    """Multiply two integers."""
    return a * b
使用 StructuredTool 类提供的函数创建工具

class langchain_core.tools.structured.StructuredTool 类用来初始化工具,其中 from_function 类方法通过给定的函数来创建并返回一个工具。from_function 类方法定义如下:

@classmethod
def from_function(
    func: Callable[..., Any] | None = None,
    coroutine: Callable[..., Awaitable[Any]] | None = None,
    name: str | None = None,
    description: str | None = None,
    return_direct: bool = False,
    args_schema: type[BaseModel] | dict[str, Any] | None = None,
    infer_schema: bool = True,
    *,
    response_format: Literal["content", "content_and_artifact"] = "content",
    parse_docstring: bool = False,
    error_on_invalid_docstring: bool = False,
    **kwargs: Any,
) -> StructuredTool:

关键参数说明

  • func:要设置的工具函数
  • coroutine:协程函数,要设置的异步工具函数
  • name:工具名称。默认为函数名称。
  • description:工具描述。默认为函数文档字符串。
  • args_schema:工具输入参数的schema。默认为 None。
  • response_format:工具响应格式。默认为 "content"
    • 如果配置为 "content",则工具的输出为 ToolMessagecontent 属性。
    • 如果配置为 "content_and_artifact",则输出应是与 ToolMessagecontent 属性与 artifact 属性相对应的二元组。

示例1:常规用法

对于用该类方法创建的工具,同样函数名、类型提示和文档字符串也都是传递给工具 Schema 的一部分,不可缺失。

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

示例2:加入配置,依赖 Pydantic 类

同样的,让工具函数不提供描述、文档字符串等需要传递给工具 Schema 的内容,如下所示:
此时可以:

  1. 使用 args_schema 参数,依赖 Pydantic 类定义并提供工具输入参数的 Schema 属性。
  2. 使用 description 参数,替代文档字符串中对于工具描述的 schema 属性。
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.description)              # 输出:两数相乘
print(calculator_tool.args)
# 输出:{'description': 'Multiply two numbers.', 'title': 'CalculatorInput', 'type': 'object', 'properties': {'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}}

示例3:加入 response_format 配置

如果希望工具区分消息内容(content)和其他工件(artifact),让大模型读取 content,而一些用来构造 content 的原始数据保存下来,若后续有一些记录、分析的步骤,就可以派上用场。需要在定义工具时指定 response_format="content_and_artifact" 参数,并确保返回一个元组 (content, artifact)

from langchain_core.tools import StructuredTool

def multiply(a: int, b: int) -> tuple[int, dict]:
    """Multiply two numbers."""
    result = a * b
    artifact = {"input_a": a, "input_b": b, "calc_time": "2026-07-07"}
    return result, artifact

calc_tool = StructuredTool.from_function(
    func=multiply,
    response_format="content_and_artifact"
)

res, art = calc_tool.invoke({"a": 4, "b": 5})
print("计算结果content:", res)
print("原始数据artifact:", art)

输出:

计算结果content: 20
原始数据artifact: {'input_a': 4, 'input_b': 5, 'calc_time': '2026-07-07'}

4.2.2 绑定工具

为了实际将这些工具绑定到聊天模型,可以使用聊天模型的 .bind_tools() 方法。如下所示:

from langchain_openai import ChatOpenAI

# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")

# ... 省略工具定义代码 ...

# 绑定工具,返回一个 Runnable 实例
tools = [add, multiply]
model_with_tools = model.bind_tools(tools)
bind_tools() 方法定义
def bind_tools(
    tools: Sequence[dict[str, Any] | type | Callable | BaseTool],
    *,
    tool_choice: dict[str, str] | Literal["auto", "none", "required", "any"] | bool | None = None,
    strict: bool | None = None,
    parallel_tool_calls: bool | None = None,
    **kwargs: Any,
) -> Runnable[
    PromptValue | str | Sequence[BaseMessage] | list[str] | tuple[str, str] | str | dict[str, Any],
    BaseMessage
]:

请求参数

  • tools:绑定到此聊天模型的工具定义列表。支持的类型为:字典、pydantic.BaseModel类、Python函数和BaseTool(如@tool装饰器创建的类)。
  • tool_choice(默认auto):要求模型调用哪个工具。可以设置为:
    • 形式为 {"tool": "<tool_name>"} 的str:调用指定工具。
    • "auto":自动选择工具(包括无工具)。
    • "none":不调用工具。
    • "any" / "required" / True:强制调用至少一个工具。
    • False:无效果,默认OpenAI的行为。
  • strict(默认None):
    • 如果为True,则保证模型输出与工具定义中提供的JSON Schema完全匹配。输入也将根据Schema进行验证。
    • 如果为False,则不会验证输入,也不会验证模型输出。
    • 如果为None,则不会将strict参数传递给模型。
  • parallel_tool_calls:默认为None,允许并行工具使用。设置为False以禁用并行工具。
  • **kwargs(Any):任何附加参数都直接传递给bind()

返回一个 Runnable 实例。
该实例支持多种格式输入:

  • 原始提示 PromptValue
  • 字符串:"上海天气如何?"
  • 消息或消息列表:[HumanMessage(content="...")]

例子:

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from typing_extensions import Annotated

@tool
def add(
    a: Annotated[int, ..., "first int"],
    b: Annotated[int, ..., "second int"]
)->int:
    """两数相加"""
    return a+b

@tool
def multiply(
    a: Annotated[int, ..., "first int"],
    b: Annotated[int, ..., "second int"]
)->int:
    """两数相乘"""
    return a*b

model = ChatOpenAI(
    model="deepseek-v4-flash",
    temperature=0,
    api_key="sk-871e1db0ec8a4c31b7a7d2b3bd8e5b3c",
    base_url = "https://api.deepseek.com/v1",
)

# 绑定工具
tools=[add,multiply]
model_with_tools=model.bind_tools(tools=tools)
# 调用工具
print(model_with_tools.invoke(input="请帮我计算一下 3+5 和 4*6 的结果,并返回结果。"))

该实例的输出:包含工具调用信息的 AIMessage

{
  "content": "好的,我同时计算这两个表达式:",
  "tool_calls": [
    {
      "id": "call_00_KrKtxxxrgp2DXr7l4Xsz7033",
      "name": "add",
      "args": {"a": 3, "b": 5},
      "type": "tool_call"
    },
    {
      "id": "call_01_txXeKOez97Cs8MLxVga26364",
      "name": "multiply",
      "args": {"a": 4, "b": 6},
      "type": "tool_call"
    }
  ],
  "response_metadata": {
    "model_name": "deepseek-v4-flash",
    "finish_reason": "tool_calls",
    "token_usage": {
      "completion_tokens": 134,
      "prompt_tokens": 385,
      "total_tokens": 519,
      "completion_tokens_details": {
        "reasoning_tokens": 22
      },
      "prompt_tokens_details": {
        "cached_tokens": 0
      }
    }
  }
}

完整的业务流程应该是:定义工具->绑定工具->调用工具(先选择后调用),对于一个问题,LLM不一定选择传入的工具

输出说明

  • AIMessage:来自AI的消息。从聊天模型返回,作为对提示(输入)的响应。
    • content:消息的内容。
    • additional_kwargs:与消息关联的其他有效负载数据。对于来自AI的消息,可能包括模型提供程序编码的工具调用。
    • response_metadata:响应元数据。例如:响应标头、logprobs、令牌计数、模型名称。

从输出结果看来,AI给出的响应是进行工具的调用,工具调用的一个关键原则是:模型根据输入的相关性决定何时使用工具。模型并不总是需要调用工具。例如,给定一个不相关的输入,模型不会调用该工具:

result = model_with_tools.invoke("hello world!")
print(result)

输出结果(AIMessage):

AIMessage(
    content='Hello there! How can I assist you today?',
    additional_kwargs={'refusal': None},
    response_metadata={
        'token_usage': {
            'completion_tokens': 10,
            'prompt_tokens': 82,
            'total_tokens': 92,
            'completion_tokens_details': {
                'accepted_prediction_tokens': 0,
                'audio_tokens': 0,
                'reasoning_tokens': 0,
                'rejected_prediction_tokens': 0
            },
            'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}
        },
        'model_name': 'gpt-4o-mini-2024-07-18',
        'system_fingerprint': 'fp_560af6e559',
        'id': 'chatcmpl-C6AoYPeJN27mQjbIsML6pIspv',
        'service_tier': 'default',
        'finish_reason': 'stop',
        'logprobs': None
    },
    id='run--072c35e6-036b-421a-9817-4417005914a2-0',
    usage_metadata={
        'input_tokens': 82,
        'output_tokens': 10,
        'total_tokens': 92,
        'input_token_details': {'audio': 0, 'cache_read': 0},
        'output_token_details': {'audio': 0, 'reasoning': 0}
    }
)

强制模型调用工具

强制调用至少一个工具。示例如下,那就需要在绑定工具时,设置 tool_choice="any",表示强制调用至少一个工具。

result = model_with_tools.bind_tools(tools, tool_choice="any").invoke("hello world!")
print(result)

输出结果(AIMessage):

AIMessage(
    content='',
    additional_kwargs={
        'tool_calls': [
            {
                'id': 'call_mBNxNY7vfAoEXEETcdEC6G1',
                'function': {'arguments': '{"a":1,"b":2}', 'name': 'add'},
                'type': 'function'
            }
        ]
    },
    response_metadata={
        'token_usage': {
            'completion_tokens': 14,
            'prompt_tokens': 84,
            'total_tokens': 98,
            'completion_tokens_details': {
                'accepted_prediction_tokens': 0,
                'audio_tokens': 0,
                'reasoning_tokens': 0,
                'rejected_prediction_tokens': 0
            },
            'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}
        },
        'model_name': 'gpt-4o-mini-2024-07-18',
        'system_fingerprint': 'fp_560af6e559',
        'id': 'chatcmpl-C6AoYPeJN27mQjbIsML6pIspv',
        'service_tier': 'default',
        'finish_reason': 'tool_calls',
        'logprobs': None
    },
    id='run--072c35e6-036b-421a-9817-4417005914a2-0',
    usage_metadata={
        'input_tokens': 84,
        'output_tokens': 14,
        'total_tokens': 98,
        'input_token_details': {'audio': 0, 'cache_read': 0},
        'output_token_details': {'audio': 0, 'reasoning': 0}
    }
)

4.2.3 工具属性

tool_calls 属性是一个列表,此属性包含执行该工具所需的一切,包括工具名称和输入参数。示例如下:

result = model_with_tools.bind_tools(tools, tool_choice="any").invoke("9乘6等于多少?")
print(result.tool_calls)

输出结果:

[{'name': 'multiply', 'args': {'a': 9, 'b': 6}, 'id': 'call_mBNxNY7vfAoEXEETcdEC6G1', 'type': 'tool_call'}]

4.2.4 将工具输出传递给聊天模型

仅仅只是成功调用了工具,聊天模型并没有返回我们真正需要的答案。此时需要:

  1. 解析模型输出的工具调用;
  2. 调用工具获取消息输入,将最终结果 AIMessage 返回。

聊天模型通常不是接受单个字符串作为输入,而是接受消息(XxxMessage)列表。因此需要将工具的返回,构造成 ToolMessage,再传输给聊天模型,如果使用 @tool 装饰器创建的工具,使用 tool.invoke(tool_calls),将自动构建一个 ToolMessage。完整示例如下:

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage

# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")

# @tool工具
@tool
def add(
    a: Annotated[int, ..., "First integer"],
    b: Annotated[int, ..., "Second integer"],
) -> int:
    """Add two integers."""
    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)

# 添加消息
messages = [HumanMessage(content="5乘3等于多少?")]
ai_msg = model_with_tools.invoke(messages) # 这里只是在选择模型

# 遍历工具调用
for tool_call in ai_msg.tool_calls:
    # 根据工具名称筛选对应的工具(不区分大小写)
    selected_tool = {"add": add, "multiply": multiply}[tool_call["name"].lower()]
    # 将工具调用传入工具执行
    tool_msg = selected_tool.invoke(tool_call)
    # 将工具消息加入消息列表
    messages.append(tool_msg)

result = model.invoke(messages)
print(result.content)

打印结果如下:

[HumanMessage(content='5乘3等于多少?'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_mBNxNY7vfAoEXEETcdEC6G1', 'function': {'arguments': '{"a": 5, "b": 3}', 'name': 'multiply'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 86, 'total_tokens': 103, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_560af6e559', 'id': 'chatcmpl-G6DSvhskItWl6TKPILuaDDUEN', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--777ef0be-55c2-411a-a4ab-104a45c22f20-0', usage_metadata={'input_tokens': 86, 'output_tokens': 17, 'total_tokens': 103, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content='15', name='multiply', tool_call_id='call_mBNxNY7vfAoEXEETcdEC6G1')]
15

从流程与代码中可以看到,实际上调用了两次聊天模型:

  1. 第一次:仅将 [HumanMessage] 发送给聊天模型进行处理,结果返回了【包含工具调用的 AIMessage】,并没有返回结果。然后执行工具,得到 ToolMessage。
  2. 第二次:将 [HumanMessage + AIMessage + ToolMessage] 消息发送给聊天模型进行处理,结果返回了【包含结果的 AIMessage】。

4.3 LangChain 提供的工具

LangChain 官方也已经给我们提供了很多现成的工具(Tool)和工具包(Toolkit)。写好的工具一般都是为了使用 LangChain 中集成的三方组件或工具而创造的,有搜索、数据库、网页浏览器等相关的工具。LangChain 中的工具实际上是继承了 BaseToolBaseToolkit。下面来简单使用其中一个工具类:

TavilySearch

TavilySearch 类可以支持我们进行搜索,Tavily 是一个专门为 AI 设计的搜索引擎,专为智能体检索与推理需求量身打造的工具。
Tavily 不仅提供了高度可编程的 API 接口,还具备显著优于传统搜索引擎的上下文相关性理解能力。能够以结构化、可解析的形式返回搜索结果,便于将检索到的信息直接用于后续的推理、生成或任务执行流程。

  • Tavily官网:https://www.tavily.com/,需魔法使用。登录完成后,新建 API Keys。
  • 点击左侧 API Playground,可以使用刚申请的 API Keys,进行搜索测试。这会返回根据查询内容得到的多条搜索结果。
  • 点击左侧 Use Cases,可以试用提供好的案例,如聊天中可以支持搜索(Chat)。

在 LangChain 中接入该搜索工具,步骤如下:

  1. 安装 langchain-tavily 包
pip install -U langchain-tavily
  1. 配置环境变量 TAVILY_API_KEY,值为申请的 API Key。

  2. 代码接入 TavilySearch 类,实现搜索功能

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_tavily import TavilySearch

# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")

# 绑定工具
tool = TavilySearch(max_results=4)  # max_results 返回的最大搜索结果
model_with_tools = model.bind_tools([tool])

# 添加HumanMessage到消息中去
messages = [
    HumanMessage("中国西安今天的天气怎么样?")
]
ai_msg = model_with_tools.invoke(messages)

# 工具调用
for tool_call in ai_msg.tool_calls:
    tool_msg = tool.invoke(tool_call)
    # 将 ToolMessage 加入消息
    messages.append(tool_msg)

result = model_with_tools.invoke(messages)
print(result.content)

结果打印示例:

根据最新气象信息,**今天(7月7日)北京的天气情况**如下:

- **天气状况**:雷阵雨 ⛈️
- **气温范围**:23°C ~ 33°C
- **风力**:南风2级
- **空气质量**:优(AQI指数约40)

**温馨提示**:
- 北京今天有雷阵雨,出门建议**携带雨具**。
- 早晚温差较大,白天最高可达33°C,体感较热,建议穿着**凉爽透气的衣物**。
- 近期北京处于多雨季节,雷雨天气可能伴有短时大风,出行请注意安全。

如果想了解未来几天的详细预报,随时可以问我哦!
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐