【LangChain】快速上手 + 聊天模型详解

🔥个人主页: 中草药

🔥专栏:【后端】登神长阶 实战落地后端全路线手册


一. 快速上手

python 复制代码
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from pyexpat.errors import messages

# 定义模型
model = ChatOpenAI(
    # 注意OpenAI与DeepSeek是兼容的所以可以用这种方式
    model="deepseek-chat",
    # 注意要在系统变量配置对应模型的apiKey或者在这里显式声明apiKey
    base_url="https://api.deepseek.com/v1",
)

messages = [
    SystemMessage("你现在叫小G"),
    HumanMessage("你是谁")
]

result = model.invoke(messages)
print(result)
print("----------")
parser=StrOutputParser()
print(parser.invoke(result))
print("----------")
chain = model | parser
print(chain.invoke(messages))

如上述可见他的基本流程是

定义大模型->定义消息列表->调用大模型

通过调试,我们可以看到result里面的内容

AIMessage:来⾃ AI 的消息。从聊天模型返回,作为对提⽰(输⼊)的响应。

content :消息的内容。

additional_kwargs:与消息关联的其他有效负载数据。对于来⾃ AI 的消息,可能包括模型提供程序编码的⼯具调⽤。

response_metadata:响应元数据。例如:响应标头、logprobs、令牌计数、模型名称。 侧重于 "响应"本⾝的信息,⽐如这次请求的 ID、使⽤的模型版本、以及服务提供商返回的所有原始元数据。它主要⽤于调试、⽇志记录和获取请求的上下⽂信息。

usage_metadata:消息的使⽤元数据,例如令牌计数。 侧重于 "资源消耗"的量化信息,即这次请求消耗了多少 Token。它主要⽤于成本计算、 监控和预算控制。

同时根据第三个打印信息我们可以看到

通过上述步骤,⽆论是调⽤⼤模型,还是输出解析,我们发现,每次都调⽤了⼀个 invoke() ⽅法,最终才会得到我们想要的结果。

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

1. Runnable 接口

Runnable 是 LangChain 为所有可执行组件提供的基础抽象(类似 "执行协议"),作为一个标准接口,它规定了组件该如何接收输入、处理逻辑、返回输出。无论组件是简单的提示模板,还是复杂的大模型调用、工具链,只要实现了 Runnable 接口,就能无缝地组合、串联、并行执行

  • Invoked(调⽤): 单个输⼊转换为输出。
  • Batched(批处理): 多个输⼊被有效地转换为输出。
  • Streamed(流式传输): 输出在⽣成时进⾏流式传输。
  • Inspected(检查): 可以访问有关 Runnable 的输⼊、输出和配置的原理图信息。
  • Composed(组合): 可以组合多个 Runnable,以使⽤ LCEL 协同⼯作以创建复杂的管道。

因此上述demo之中无论是我们定义的语言模型 model ,还是输出解析器 StrOutputParser 都是Runnable 接口,都调用了Invoke能力

2. LCEL

LCEL(LangChain Expression Language)是 LangChain 框架的核心声明式组合语言 ,它基于 Runnable 接口构建,提供了一种简洁、统一的方式来组合 LangChain 组件,构建从简单到复杂的 LLM 应用工作流Langchain。你可以把它理解为 LangChain 的 "组件胶水语言",让不同组件像乐高积木一样灵活拼接。

从现有的Runnable对象构建新的Runnable对象,而这个新的被称为RunnaleSequence,表示可运行序列。

python 复制代码
chain = model | parser

两个Runnable对象去构建了一个RunnableSequence,实际上的LangChain重载了 | 的运算,相当于

python 复制代码
from langchain_core.runnables import RunnableSequence
chain = RunnableSequence(first=model, last=parser)

二. 聊天模型

1. 定义聊天模型

聊天模型(Chat Model)是 LangChain 中专门处理对话式交互的组件,区别于传统的纯文本补全模型(LLM):

  • LLM:输入纯文本,输出纯文本(如 GPT-3 的文本补全);
  • Chat Model :输入结构化的聊天消息(如系统指令、用户提问、助手回复),输出结构化的 AI 消息,更贴合实际的对话场景。

1.1 通过API的方式定义模型

python 复制代码
from langchain_openai import ChatOpenAI

# 初始化Chat Model(需配置环境变量 OPENAI_API_KEY)
chat_openai = ChatOpenAI(
    model="gpt-3.5-turbo",  # 模型名称
    temperature=0.7,        # 随机性(0-1,0最确定)
    max_tokens=1000         # 最大生成token数
)

常见参数

参数名 参数描述
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。如果未传入,将从环境变量 OPENAI_ORG_ID 中读取。
...... (其他未列出的参数)

1.2 采用 init_chat_model 的方式

上⾯的 ChatOpenAI ⽤于明确创建 OpenAI 聊天模型的实例。⽽ init_chat_model() 是⼀个⼯⼚函数,它可以初始化多种⽀持的聊天模型(如 OpenAI、Anthropic、FireworksAI 等),不仅仅是 OpenAI 的聊天模型。

init_chat_model() 函数

python 复制代码
langchain.chat_models.base.init_chat_model(
    model: str,
    *,
    model_provider: str | None = None,
    configurable_fields: Literal[None] = None,
    config_prefix: str | None = None,
    **kwargs: Any,
) → BaseChatModel
参数名 参数描述
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"]["{config_prefix}_{param}"] 字段设置配置项。 - 配置为空字符串,那么模型将可以通过 config["configurable"]["{param}"] 字段设置配置项。
temperature 采样温度,温度值越高,AI 回答越天马行空;温度越低,回答越保守靠谱。
max_tokens 要生成的最大令牌数
timeout 请求超时时间
max_retries 最大重试次数
openai_api_key / api_key OpenAI API 密钥。如果未传入,将从环境变量中读取 OPENAI_API_KEY
base_url API 请求的基本 URL。
...... (其他未列出的参数)

注意

函数返回的是一个与指定的 model_name 与 model_provider 相对应的BaseChatModel

如果模型是可配置的,则返回 一个聊天模型模拟器,该模拟器在传入配置后,于运行时才会初始化底层模型

示例1 基本用法

python 复制代码
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")

返回结果

bash 复制代码
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 可配置模型

python 复制代码
config_model1 = init_chat_model()

messages = [
    SystemMessage(content="请补全一段故事,100个字以内:"),
    HumanMessage(content="一只狗正在__?")
]

result = config_model1.invoke(
    input=messages,
    config={
        "configurable":{
            "model": "deepseek-chat"
        }
    }
)
print(result.content)

示例3 动态配置

python 复制代码
# 可配置模型模拟器
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")

1.3 本地部署LLM应以聊天模型

python 复制代码
from langchain_ollama import ChatOllama

ollama_model = ChatOllama(model="deepseek-r1:70b",

base_url='http://192.168.100.220:11434')

result = ollama_model.invoke("what's your name?")

print(result)

2. 工具调用

纯聊天模型(比如 GPT-3.5/4、DeepSeek)的短板是:只有训练数据里的知识,没有实时数据 (比如今天的天气)、计算能力 (比如复杂算术)、外部系统交互能力(比如查你的数据库)。

工具调用就是给聊天模型 "装插件":

  • 你先定义好可用的工具(比如 "查天气""执行加法计算""查股票价格");
  • 聊天模型会自主分析用户问题,判断:✅ "这个问题需要调用工具吗?"✅ "需要调用哪个 / 哪些工具?"✅ "调用工具需要传什么参数?"
  • 模型生成工具调用指令,执行工具并获取结果;
  • 模型把工具结果整合到回答里,最终返回给用户。

举个直观例子:

用户问:"今天北京的气温加 10 是多少?"

  1. 模型判断:需要先调用 "查北京气温" 工具,再调用 "加法计算" 工具;
  2. 调用 "查气温" 工具,得到结果:北京今天 15℃;
  3. 调用 "加法计算" 工具,参数是 15+10,得到 25;
  4. 模型整合结果:"今天北京气温是 15℃,加 10 后是 25℃。"

作用

  • 扩展能力边界:借助工具完成计算、联网搜索、数据库查询等自身无法实现的任务。
  • 保证信息实时性:获取训练数据中没有的最新信息,避免回答过时或错误。
  • 处理复杂任务:将复杂请求拆解为多步骤,协调不同工具协作完成(这一点在 Agent 智能体中体现得尤为明显)。
  • 连接现有系统:把企业内部的系统、API 和数据库封装成工具,让 LLM 成为自然语言驱动的统一接口,大幅提升自动化与集成能力。

2.1 创建工具

定义工具的底层逻辑:工具名称、工具描述、工具参数

使用@tool装饰器创建工具

python 复制代码
from langchain_core.tools import tool

@tool
def calculate(a: int, b: int)-> int:
    """
    Ryan式计算结果

    Args:
        a: 第一个整数
        b: 第二个整数
    """
    return a+b+1

print(calculate.invoke({"a": 1, "b": 2}))

注意

该装饰器固定使用函数名称作为工具名称

函数的文档字符串(docstring)必须写清楚:模型会通过这个描述判断什么时候用这个工具、传什么参数(这是工具调用的关键!)

因此 函数名,类型提示,文档字符串都是传递给工具Schema的一部分

在大语言模型(LLM)的工具调用场景里,工具的 Scheme(也叫工具协议 / 工具定义规范),是指给模型提供的一份 "工具使用说明书",它用结构化的方式定义了工具的功能、参数、返回值等信息,让模型能清晰理解工具的用途和调用方式。

简单来说,Scheme 就是模型和工具之间的 "沟通语言",确保模型能准确判断何时调用工具、如何传参。

核心组成部分(以 LangChain 为例)

一个完整的工具 Scheme 通常包含以下关键信息:

工具元信息

  • name:工具的唯一标识名称(如get_weather),模型用它来指定要调用的工具。
  • description:工具的功能描述(如 "获取指定城市的实时气温"),这是模型判断是否需要调用该工具的核心依据。

参数定义

  • parameters:定义工具所需的输入参数,包括参数名、类型(如str/int)、是否必填、参数描述。
  • 例如:get_weather工具的参数city是字符串类型,必填,描述为 "城市名称(如北京、上海)"。

返回值定义

  • 说明工具执行后返回的数据类型和格式(如 "返回该城市的实时气温字符串"),帮助模型理解工具结果并整合到回答中。

因此,工具的Schema需要解析google风格的文档字符串去获取参数描述

google风格字符串具有极高的可读性和简洁性

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

    Args:
        url (str): 要从中获取数据的URL。
        retries (int, optional): 失败时重试的次数。默认为3。
    Returns:
        dict: 从URL解析的JSON响应。
    """
# ... 函数实现 ...

此外传递信息给工具的schema还有这些方式

1. 依赖pydantic

python 复制代码
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class CaculateInput(BaseModel):
    """Ryan计算"""
    a: int = Field(..., description="第一个参数")
    b: int = Field(..., description="第二个参数")

@tool(args_schema=CaculateInput)
def calculate(a: int, b: int) -> int:
    return a + b + 2

print(calculate.invoke({"a": 1, "b": 2}))

class CaculateInput(BaseModel):定义一个继承自 Pydantic BaseModel的类,作为calculate工具的输入参数 schema(也就是之前聊的 "工具 Scheme");

BaseModel:所有 Pydantic 模型的基类,继承它就拥有了数据验证能力。

Field(...):用于给字段添加约束和描述

  • ... 表示该字段是必填项,不传会直接报错。
  • description 是字段的说明,在 LangChain 中会成为工具 Scheme 的一部分,让 LLM 理解参数含义。

2. 依赖于Annotated

也可以使用Annotated去传递工具Schema

python 复制代码
@tool
def calculate(
        a: Annotated[int,...,"第一个参数"],
        b: Annotated[int,...,"第二个参数"]
)-> int:
    """Ryan计算"""
    return a+b+3

print(calculate.invoke({"a": 1, "b": 2}))

Annotated 是一个核心工具,它的本质是给类型添加 "元数据标签",让框架能读懂你的类型约束、描述等额外信息。

2. 使用StructuredTool来初始化工具

java 复制代码
classmethod from_function(
    func: Callable | 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" ,则⼯具的输出为 ToolMessage 的 content 属性。
  • 对于 HumanMessage 、 AIMessage 已经⻅过,分别表⽰ ⽤⼾消息 和 AI消息响应 ,
  • 对于 ToolMessage ,它表⽰对应⼯具⻆⾊所发出的消息。
  • 如果配置为 "content_and_artifact" ,则输出应是与 ToolMessage 的 content 属性与 artifact 属性相对应的⼆元组。

常规用法

python 复制代码
def calculate(a:int, b:int) -> int:
    """Ryan计算"""
    return a+b+2

calculate_tool = StructuredTool.from_function(calculate)

print(calculate_tool.invoke({"a": 1, "b": 2}))

加上配置,依赖pydantic

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) # 输出:Calulate

如上可见,我们上述的方式都返回的原生数据,而简单的content并不能满足实际所需,因此

我们需要指定 response_format="content_and_artifact

python 复制代码
class AddInput(BaseModel):
    a:int = Field(description="第一个整数")
    b:int = Field(description="第二个整数")


def add(a: int, b: int) -> Tuple[str, List[int]]:
    nums = [a, b]
    content = f"{nums}相加的结果是{a + b}"
    return content, nums

add_tool = StructuredTool.from_function(
    func=add,
    name="ADD",             # 工具名
    description="两数相加",   # 工具描述
    args_schema=AddInput,   # 工具参数
    response_format="content_and_artifact"
)

# 模拟大模型调用姿势
print(add_tool.invoke(
    {
        "name": "ADD",
        "args": {"a": 3, "b": 4},
        "type": "tool_call",  # 必填
        "id": "111",  # 必填, 用来将工具调用请求和结果关联起来。
    }
))

2.2 绑定、调用工具

为了实现这些功能给大模型

我们需要用到 bind_tools() 方法

python 复制代码
bind_tools(
    tools: Sequence[dict[str, Any] | type | Callable | BaseTool],
    *,
    tool_choice: dict | 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(默认空):要求模型调用哪个工具。可以设置为:

    • 形式为 '<tool_name>' 的 str:调用 <tool_name> 工具。
    • 'auto':自动选择工具(包括无工具)。
    • 'none':不调用工具。
    • 'any''required'True:强制调用至少一个工具。
    • FalseNone:无效果,默认 OpenAI 的行为。
  • strict(默认空):

    • 如果为 True,则保证模型输出与工具定义中提供的 JSON Schema 完全匹配。输入也将根据提供的 Schema 进行验证。
    • 如果为 False,则不会验证输入,也不会验证模型输出。
    • 如果为 None,则不会将 strict 参数传递给模型。
  • parallel_tool_calls:默认为 None,允许并行工具使用。设置为 False 以禁用并行工具。

  • kwargs (Any):任何附加参数都直接传递给 bind()

返回值:

  • 返回一个 Runnable 实例。
    • 该实例支持多种格式输入:
      • 原始提示 PromptValue
      • 字符串: "上海天气如何?"
      • 消息或消息列表: [HumanMessage(content="...")]
    • 该实例的输出:
      • 包含工具调用信息的 AIMessage

示例

针对下述代码

python 复制代码
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from pydantic import BaseModel, Field


class CalculateInput1(BaseModel):
    """Ryan计算"""
    a: int = Field(..., description="第一个参数")
    b: int = Field(..., description="第二个参数")

class CalculateInput2(BaseModel):
    """郑式计算"""
    a: int = Field(..., description="第一个参数")
    b: int = Field(..., description="第二个参数")

@tool(args_schema=CalculateInput1)
def calculate_with_ryan(a: int, b: int) -> int:
    return a + b + 2

@tool(args_schema=CalculateInput2)
def calculate(a: int, b: int) -> int:
    return a * b + 2

tools=[calculate_with_ryan, calculate]
model = init_chat_model("deepseek-chat", model_provider="deepseek", temperature=0.3)
model_with_tools=model.bind_tools(tools=tools)

print(model_with_tools.invoke("2和3的Ryan计算结果和郑式计算结果是多少"))

分析结果

python 复制代码
【核心响应内容】
content='我来帮你计算2和3的Ryan计算结果和郑式计算结果。'

【附加参数】与消息关联的其他负载有效数据
additional_kwargs={'refusal': None}

【响应元数据】
response_metadata={
    'token_usage': {
        'completion_tokens': 119,
        'prompt_tokens': 400,
        'total_tokens': 519,
        'completion_tokens_details': None,
        'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 384},
        'prompt_cache_hit_tokens': 384,
        'prompt_cache_miss_tokens': 16
    },
    'model_provider': 'deepseek',
    'model_name': 'deepseek-chat',
    'system_fingerprint': 'fp_eaab8d114b_prod0820_fp8_kvcache',
    'id': 'b57ed2ac-ab9a-42bd-8d7e-8477c2c41f96',
    'finish_reason': 'tool_calls',
    'logprobs': None
}

【LangChain运行ID】
id='lc_run--019c1827-2c3d-7070-8717-26c9c7f4877b-0'

【工具调用列表】
tool_calls=[
    {'name': 'calculate_with_ryan', 'args': {'a': 2, 'b': 3}, 'id': 'call_00_efAoVU3LvoHgXASJsSoh291k', 'type': 'tool_call'},
    {'name': 'calculate', 'args': {'a': 2, 'b': 3}, 'id': 'call_01_OMQUKSR38dfPUJOcMP0zo9o4', 'type': 'tool_call'}
]

【无效工具调用】
invalid_tool_calls=[]

【使用元数据】
usage_metadata={
    'input_tokens': 400,
    'output_tokens': 119,
    'total_tokens': 519,
    'input_token_details': {'cache_read': 384},
    'output_token_details': {}
}

模型会根据输入的相关性去调用工具,我们也可以强行调用工具

python 复制代码
model_with_tools = model.bind_tools(tools, tool_choice="any")

我们知道输出的结果是一个AIMessage,如果调用工具之后,会有一个参数叫做 tool_calls 属性

我们可以看到结果里并没有我们想要的结果,因此我们需要将工具输出传递给聊天模型

python 复制代码
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from pydantic import BaseModel, Field



class CalculateInput1(BaseModel):
    """Ryan计算"""
    a: int = Field(..., description="第一个参数")
    b: int = Field(..., description="第二个参数")

class CalculateInput2(BaseModel):
    """郑式计算"""
    a: int = Field(..., description="第一个参数")
    b: int = Field(..., description="第二个参数")

@tool(args_schema=CalculateInput1)
def calculate_with_ryan(a: int, b: int) -> int:
    return a + b + 2

@tool(args_schema=CalculateInput2)
def calculate(a: int, b: int) -> int:
    return a * b + 2

tools=[calculate_with_ryan, calculate]
model = init_chat_model("deepseek-chat", model_provider="deepseek", temperature=0.3)
model_with_tools=model.bind_tools(tools=tools,tool_choice="any")

messages = [
    HumanMessage("2和3的Ryan计算结果和郑式计算结果是多少"),
]

ai_message = model_with_tools.invoke(messages)
messages.append(ai_message)

# 构造ToolMessage
# {'name': 'calculate_with_ryan', 'args': {'a': 2, 'b': 3}, 'id': 'call_00_DdabN1GMiupyjYDIYubXxk4Q', 'type': 'tool_call'}
# {'name': 'calculate', 'args': {'a': 2, 'b': 3}, 'id': 'call_01_LNeNF9O8duWJnW5rKtaN4D0r', 'type': 'tool_call'}
for tool_call in ai_message.tool_calls:
    selected_tool = {"calculate_with_ryan": calculate_with_ryan,
                     "calculate": calculate}[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    messages.append(tool_msg)

print(model.invoke(messages))

我们可以看到整个流程我们实际调用了两次大模型

第⼀次:仅将【 HumanMessage 】发送给聊天模型进⾏处理,结果返回了【包含⼯具调⽤的AIMessage 】,并没有返回我们想要的结果。然后我们执⾏⼯具,得到 ToolMessage 。

第⼆次:将【 HumanMessage + AIMessage + ToolMessage 】消息记录发送给聊天模型进⾏处理,结果返回了【包含结果的 AIMessage 】

2.3 LangChain提供工具

我们并不需要手搓所有的工具,他提供了很多现成的

https://docs.langchain.com/oss/python/integrations/providers/overview

3. 结构化输出

结构化输出解决的是从字符串到对象的范式转换,要想使用结构化输出的能力

LangChain 提供了⼀种⽅法 .with_structured_output() ,该⽅法需要先定义输出结构,然后执⾏通过 .with_structured_output() 得到的 Runnable 实例

with_structured_output()

bash 复制代码
with_structured_output(
    schema: dict[str, Any] | type[_BM] | type | None = None,
    *,
    method: Literal['function_calling', 'json_mode', 'json_schema'] =
'json_schema',
    include_raw: bool = False,
    strict: bool | None = None,
    **kwargs: Any,
) → Runnable[PromptValue | str | Sequence[BaseMessage | list[str] |
tuple[str, str] | str | dict[str, Any]], dict | _BM]
  • schema:表示输出结构。可以传入为:JSON、TypedDict、Pydantic、OpenAI 函数 / 工具
  • method:表示 LLM 的生成方法:
    • json_schema(默认):表示使用 OpenAI 的结构化输出 API。
    • function_calling:使用 OpenAI 的工具调用(以前称为函数调用)
    • json_mode:使用 OpenAI 的 JSON mode。请注意,如果使用 json_mode,则必须在模型调用中包含将输出格式化为所需 schema 的说明。
  • include_raw
    • 如果为 False(默认),则仅返回解析的结构化输出。如果在模型输出解析过程中发生错误,则会引发错误。
    • 如果为 True,则将返回原始模型响应(BaseMessage)和解析的模型响应。如果在输出解析过程中发生错误,它也会被捕获并返回。最终输出始终是带有键 "raw"、"parsed" 和 "parsing_error" 的字典。
  • strict
    • 如果为 True,保证模型输出与 schema 完全匹配。输入 schema 也将根据支持的 schema 进行验证。
    • 如果为 False,输入 schema 将不会被验证,模型输出也不会被验证。
    • 如果为 None(默认),则不会将 strict 参数传递给模型。
  • tools :要绑定到聊天模型的工具列表。要求:method'json_schema'strict=Trueinclude_raw=True。则生成的 AIMessage 将在 'raw' 中包含工具调用
  • kwargs (Any):任何附加参数都直接传递给 bind ()。

返回一个 Runnable 实例。将来执行时:

  • 如果 include_raw=False
    • schema是 Pydantic 类,则 Runnable 会输出 Pydantic 对象。
    • 否则 Runnable 输出一个字典。
  • 如果 include_raw=True,则 Runnable 输出一个带有键的字典:
    • 'raw':BaseMessage
    • 'parsed':如果出现解析错误,则为 None,否则类型取决于如上所述的 schema
    • 'parsing_error':Optional[BaseException]

3.1 返回Pydantic对象

我们可以设置执⾏ Runnable 后的输出结果指定为 Pydantic 类,这将返回⼀个 Pydantic 对象。

python 复制代码
from typing import Optional

from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field

model = init_chat_model("deepseek-chat", model_provider="deepseek", temperature=0.3)

class Joke(BaseModel):
    """给用户讲的一个笑话"""

    setup: str = Field(description="这个笑话的开头")
    punchline: str = Field(description="这个笑话的妙语")
    rating: Optional[int] = Field(default=None, description="从1-10分,给这个笑话评分")

structured_model = model.with_structured_output(Joke)

print(structured_model.invoke("给我讲一个关于狗的笑话"))

3.2 返回TypedDict

python 复制代码
from langchain_openai import ChatOpenAI
from typing import Optional
from typing_extensions import Annotated, TypedDict

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

# 定义输出结构: TypedDict
class Joke(TypedDict):
    """给⽤⼾讲⼀个笑话。"""

    setup: Annotated[str, ..., "这个笑话的开头"]
    punchline: Annotated[str, ..., "这个笑话的妙语"]
    rating: Annotated[Optional[int], None, "从1到10分,给这个笑话评分"]

structured_model = model.with_structured_output(Joke)
result = structured_model.invoke("给我讲⼀个关于唱歌的笑话")
print(result)

结果

python 复制代码
{'setup': '为什么歌⼿总是带⼀把伞?', 'punchline': '因为他们怕下⾬时会错过⾳调!',
'rating': 7}

如果包含 include_raw=True

python 复制代码
{
    'raw': AIMessage(content='{"setup":"你知道为什么歌⼿总是喜欢在吃饭的时候唱歌
吗?","punchline":"因为他们想要增加⾃⼰的'调味'!","rating":7}', additional_kwargs=
{'parsed': None, 'refusal': None}, response_metadata={'token_usage':
{'completion_tokens': 40, 'prompt_tokens': 173, 'total_tokens': 213,
'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-C6WinVyn0c2VI2wcNQerfrmI3GdNA', 'service_tier': 'default',
'finish_reason': 'stop', 'logprobs': None}, id='run--00d306c7-8797-4f20-90cb-
65e5b32358b4-0', usage_metadata={'input_tokens': 173, 'output_tokens': 40,
'total_tokens': 213, 'input_token_details': {'audio': 0, 'cache_read': 0},
'output_token_details': {'audio': 0, 'reasoning': 0}}),
    'parsed': {'setup': '你知道为什么歌⼿总是喜欢在吃饭的时候唱歌吗?', 'punchline':
'因为他们想要增加⾃⼰的'调味'!', 'rating': 7},
    'parsing_error': None}

3.3 返回Json

python 复制代码
from langchain_openai import ChatOpenAI

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

json_schema = {
    "title": "joke",
    "description": "给⽤⼾讲⼀个笑话。",
    "type": "object",
    "properties": {
     "setup": {
        "type": "string",
        "description": "这个笑话的开头",
    },
    "punchline": {
        "type": "string",
        "description": "这个笑话的妙语",
    },
    "rating": {
            "type": "integer",
            "description": "从1到10分,给这个笑话评分",
            "default": None,
        },
    },
    "required": ["setup", "punchline"],
}

structured_model = model.with_structured_output(json_schema)
result = structured_model.invoke("给我讲⼀个关于唱歌的笑话")
print(result)
python 复制代码
{'setup': '为什么唱歌的⼈总是很开⼼?', 'punchline': '因为他们总是有很多⾳符可供选
择!', 'rating': 7}

3.4 组合输出格式

python 复制代码
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Optional, Union

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

class Joke(BaseModel):
    """给⽤⼾讲⼀个笑话。"""

    setup: str = Field(description="这个笑话的开头")
    punchline: str = Field(description="这个笑话的妙语")
    rating: Optional[int] = Field(
        default=None, description="从1到10分,给这个笑话评分"
)

class ConversationalResponse(BaseModel):
    """以对话的⽅式回应。待⼈友善,乐于助⼈。"""
    response: str = Field(description="对⽤⼾查询的会话响应")

class FinalResponse(BaseModel):
    final_output: Union[Joke, ConversationalResponse]

structured_model = model.with_structured_output(FinalResponse)
result = structured_model.invoke("给我讲⼀个关于唱歌的笑话")
print(result)
result = structured_model.invoke("你好")
print(result)
python 复制代码
final_output=Joke(setup='为什么歌⼿总是带着梯⼦?', punchline='因为他们想要达到更⾼的
⾳调!', rating=7)
final_output=ConversationalResponse(response='你好!有什么我可以帮助你的吗?')

实际应用示例

python 复制代码
from langchain.chat_models import init_chat_model
from langchain_openai import ChatOpenAI
from typing import Optional
from pydantic import BaseModel, Field
from langchain_core.messages import HumanMessage, SystemMessage

# 定义大模型
model = init_chat_model("deepseek-chat", model_provider="deepseek", temperature=0.3)

class Person(BaseModel):
    """一个人的信息。"""

    # 注意:
    # 1. 每个字段都是 Optional "可选的" ------ 允许 LLM 在不知道答案时输出 None。
    # 2. 每个字段都有一个 description "描述" ------ LLM使用这个描述。
    # 有一个好的描述可以帮助提高提取结果。
    name: Optional[str] = Field(default=None, description="这个人的名字")
    hair_color: Optional[str] = Field(default=None, description="如果知道这个人头发的颜色")
    skin_color: Optional[str] = Field(default=None, description="如果知道这个人的肤色")
    height_in_meters: Optional[str] = Field(default=None, description="以米为单位的高度")


structured_model = model.with_structured_output(schema=Person)
messages = [
    SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null"),
    HumanMessage(content="史密斯身高6英尺,金发。")
]
result = structured_model.invoke(messages)
print(result)
python 复制代码
name='史密斯' hair_color='⾦发' skin_color=None height_in_meters='1.83'

4. 流式输出

4.1 stream()同步输出

stream()方法会返回一个迭代器,他会在输出的时候同步输出消息块,可以使用for循环处理

4.2 异步输出 使用astream()

流式输出的更多的场景是一个异步场景

异步方式:asyncio,协程,事件循环

asyncio 是 Python 异步编程的标准库 ,协程是异步的任务单元 ,事件循环是异步任务的调度器 ------ 三者的关系:事件循环(管家)调度多个协程(可暂停的任务),asyncio 封装了这一切的工具集。

协程(Coroutine):可暂停的 "特殊函数"

  • 通俗解释:像 "能中途暂停、事后恢复" 的任务。比如你煮泡面(协程),煮水时不用干等,先去切火腿肠(另一个协程),水开了再回来继续煮面。

  • 语法特征

    • async def 定义(区别于普通 def);
    • 内部用 await 标记 "需要暂停的地方"(比如等网络请求、等延迟)。
  • 极简例子

    python 复制代码
    # 定义一个协程(可暂停的任务)
    async def make_noodles():
        print("开始煮水...")
        await asyncio.sleep(2)  # 暂停2秒(模拟等水开,非阻塞)
        print("水开了,下面!")
        return "泡面做好了"
  • 关键区别:普通函数调用后 "一路执行到底",协程调用后不会立即执行,需要交给事件循环调度。

python 复制代码
from langchain_openai import ChatOpenAI
# 定义⼤模型
model = ChatOpenAI(model="gpt-4o-mini")

# 异步调⽤
async def async_stream():
    print("=== 异步调⽤ ===")
    async for chunk in model.astream("讲⼀个50字的笑话"):
        print(chunk.content, end="|", flush=True)

import asyncio
asyncio.run(async_stream())

4.3 使用StrOutputParser

前面的 runnale 接口介绍

我们可以看出来流式传输并不算是聊天模型独有的能力

bash 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

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

# 定义输出解析器
parser = StrOutputParser()

# 定义链
chain = model | parser

for chunk in chain.stream("写⼀段关于爱情的歌词,需要5句话"):
print(chunk, end="|", flush=True)

4.4 自定义流式输出解析器

前面提到聊天模型的stream()方法返回的是一个迭代器

python 复制代码
from typing import Iterator, List

from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 组件1:聊天模型
model = init_chat_model("deepseek-chat", model_provider="deepseek")
# 组件2:输出解析器(str)
parser = StrOutputParser()

# 自定义生成器
def split_into_list(input: Iterator[str]) -> Iterator[List[str]]:
    buffer = ""
    for chunk in input:
        buffer += chunk
        # 遇到 。 需要刷新
        while "。" in buffer:
            # 找到 。的位置
            stop_index = buffer.index("。")
            # yield 用于创造生成器
            # yield 是生成器核心:不会一次性返回所有结果,而是"按需生成"
            yield [buffer[:stop_index].strip()]
            # 是分隔「起始索引」和「结束索引」的必选标记
            buffer = buffer[stop_index + 1 :]
    # 处理buffer最后几个字
    yield [buffer.strip()]


# 定义链
chain = model | parser | split_into_list

# 返回一个迭代器,产生的消息块
for chunk in chain.stream("写一段关于爱情的歌词,需要5句话,每句话用中文句号隔开。"):
    # chunk: AIMessageChunk
    # print(chunk.content, end="|", flush=True)
    # 使用 parser,结果就是 str
    print(chunk, end="|", flush=True)
bash 复制代码
['在星空下许下承诺的誓⾔']|['你的笑容如同晨曦,温暖了我的⼼']|['⽆论时光如何流转,我愿与
你携⼿共⾏']|['爱情是我们⼼中永恒的旋律']|['每⼀次相拥,都是⼀场甜蜜的重逢']|['']|

种一棵树最好的时间是十年前,其次是现在。 ------ 丹比萨・莫约

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸

相关推荐
@atweiwei7 小时前
用 Rust 构建agent的 LLM 应用的高性能框架
开发语言·后端·rust·langchain·eclipse·llm·agent
abigale038 小时前
Langchain入门到实战开发智能体教程(LLM+RAG+OpenAI+Agent)-下
langchain·prompt
Csvn10 小时前
🌟 LangChain 30 天保姆级教程 · Day 16|文档加载器大合集!PDF、Word、网页、数据库一键读取,构建你的知识库!
python·langchain
前进的李工11 小时前
智能Agent实战指南:从入门到精通(工具)
开发语言·人工智能·架构·langchain·agent·tool·agentexecutor
chaors11 小时前
LangGraph 入门到精通0x00:HelloLangGraph
langchain·llm·agent
老王熬夜敲代码12 小时前
LAngChain工具接入
langchain
怕浪猫12 小时前
第12章 工具(Tools)与函数调用(LangChain实战)
langchain·aigc·ai编程
老王熬夜敲代码12 小时前
接入工具代码讲解
langchain
是小蟹呀^1 天前
【总结】LangChain中工具的使用
python·langchain·agent·tool