目录
[1.1通过 API 定义聊天模型](#1.1通过 API 定义聊天模型)
[返回JSON Schema](#返回JSON Schema)
1.定义聊天模型
大预言模型 (LLM) 在各种与语言相关的任务(例如文本生成、翻译、摘要、问答等)中表现出色。
现代 LLM 通常通过聊天模型接口访问,该接口将消息列表作为输入,并返回消息作为输出,而不是使用纯文本。
这里需要注意 LLM 与 LangChain 中 聊天模型 的关系:
-
在 LangChain 的官方文档中,认为 LLM大多数是纯文本补全模型。这些纯文本模型封装的 API 接受一个字符串提示作为输入,并输出一个字符串补全结果(实际上 LLM 还包括多模态输入)。OpenAI 的 GPT-5 就是作为 LLM 来实现的。
-
LangChain 中的 聊天模型 通常由 LLM 提供支持,但经过专门调整以用于对话。关键在于,它们不是接受单个字符串作为输入,而是接受聊天消息列表,并返回⼀一条AI 消息作为输出。
1.1通过 API 定义聊天模型
ChatOpenAI 定义聊天模型在快速上手模块中已经涉及。
class langchain_openai.chat_models.base.ChatOpenAI 是 LangChain 为 OpenAI 的聊天模型(如 gpt-5 , gpt-5-mini )提供的具体实现类。
其继承了 class langchain_openai.chat_models.base.BaseChatOpenAI ,且BaseChatOpenAI 实现了标准的 Runnable 接口。
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...
)
invoke() 调用
关于 Runnable 接口中的 .invoke() 调用。该方法是将单个输入转换为对应的输出。例如对于聊天模型来说,就是根据用户的问题输入,输出相应的答案。
invoke() 方法定义:
请求参数:
-
input :输入一个 Runnable 实例
-
config (默认空):用于 Runnable 的配置。
返回值:
- 返回一个 Runnable 实例
1.2LCEL核心概念与语法
LCEL 全称 LangChain Expression Language,是 LangChain 框架的声明式编排 DSL(领域特定语言),核心用管道符 | 组合 Runnable 组件(如 Prompt、LLM、Parser、Retriever 等),实现简洁、可维护的 LLM 应用工作流,替代旧版 Chain API,支持流式、异步、并行等高级能力。
-
核心符号 :用
|串联组件,形成 "输入→组件 1→组件 2→...→输出" 的处理流,如prompt | llm | parser,语义同 UNIX 管道。 -
Runnable 接口 :所有组件均实现
Runnable,统一提供invoke()(单次执行)、stream()(流式输出)、batch()(批量处理)、ainvoke()(异步执行)等方法,保证跨组件一致性与可组合性。 -
声明式编程:描述 "要做什么" 而非 "怎么做",LangChain 自动优化执行(如并行调度、异步处理),减少样板代码。
使用示例:
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
#1.定义模型
model=ChatOpenAI(model="gpt-4o-mini")
#2.定义消息
# HumanMessage 用户消息
# SystemMessage 系统消息
# 返回的结果为 AIMessage
messages=[
SystemMessage("请翻译以下句子:"),
HumanMessage("hello!")
]
#3.定义解析器
parser = StrOutputParser()
# print(parser.invoke(result))
#4.用LCEL组合链
chain=model | parser
# 执行链
print(chain.invoke(messages))
1.3ChatOpenAI参数解释
temperature:控制 LLM 生成内容的「随机性 / 创造性」和「确定性 / 严谨性」的权重值。
取值范围:0 ~ 2(主流取值0 ~ 1,极少数场景会用到 1 以上)
不同温度值的效果
✅温度 = 0 → 极致确定性(最常用:问答 / 严谨任务)
-
模型只选概率最高的那个 token ,完全不随机,每次调用生成的内容完全一致
-
优点:回答精准、逻辑严谨、无废话、一致性极强
-
缺点:内容生硬、死板,几乎没有创造性,偶尔会出现重复文本
-
适用场景:事实问答、代码生成、数学计算、专业知识输出、精准翻译、RAG 检索后的答案生成(LCEL 做 RAG 必配 0)
✅ 0 < 温度 < 0.5 → 低随机性(次常用:平衡严谨 + 灵活)
-
模型优先选高概率词,偶尔选次高概率词,内容基本一致,细节略有不同
-
优点:兼顾精准和一点点灵活,不会生硬,也不会跑偏
-
适用场景:工作总结、产品说明、客服话术、常规文案、 指令 执行
✅ 0.5 < 温度 < 1 → 中随机性(创作类任务)
-
模型会随机挑选中高概率的词,每次生成的内容差异明显,逻辑依然在线
-
优点:创造性强、内容丰富、表达多样、有文采
-
缺点:偶尔会出现无关的细节,严谨性下降
-
适用场景:文案创作、故事续写、诗歌生成、脑暴想法、营销文案、角色对话
✅ 温度 ≥ 1 → 高随机性(创意类任务)
-
模型会大量选择低概率的词,生成的内容天马行空、脑洞大开,甚至逻辑跳脱
-
温度 = 2 时:随机性拉满,内容可能前言不搭后语,出现无意义的词汇组合
-
优点:极致的创造力
-
缺点:逻辑混乱、准确性极低、容易偏离主题
-
适用场景:纯艺术创作、脑洞故事、创意灵感、无厘头内容
✅ 温度 > 2 → 不推荐使用
生成的内容基本无逻辑,属于 "乱序拼接",几乎没有实际应用价值。
代码运行示例:
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
#1.定义模型
model=ChatOpenAI(
model="gpt-4o-mini",
temperature=0 #采样温度
)
#2.定义消息
messages=[
SystemMessage("请回答:"),
HumanMessage("什么是LCEL")
]
#3.定义解析器
parser = StrOutputParser()
# 4.用LCEL组合链
chain=model | parser
# 执行链
print(chain.invoke(messages))
采样温度为0时的输出:

采样温度为2时的输出:

max_tokens:大语言模型( LLM )的生成长度上限控制参数,作用是:限制模型「最多能为你生成多少个 Token 」,一旦生成的 Token 数量达到这个阈值,模型会立刻停止输出,哪怕内容还没说完、没结尾。
-
Token 是大模型的最小文本处理单位,不是「汉字」也不是「英文单词」
-
中文:1 个 Token ≈ 1 个汉字(含标点,比如:你好 → 2 个 Token,吃饭吗?→3 个 Token)
-
英文:1 个 Token ≈ 0.75 个英文单词(比如:hello →1 个 Token,I love you →3 个 Token)
2.聊天模型------调用工具
2.1创建工具
LangChain 的 Tools :是可以让 大语言模型 ( LLM ) 主动「调用」的外部功能 / 函数 / 服务的封装 ,本质是让 LLM 拥有 「思考 + 执行」的能力,而不只是「回答问题」。
Tools(工具 / 函数调用) 是 LangChain 最核心的高阶能力,也是把「单纯的大模型对话」升级成「能执行任务、调用工具、联网、计算、查数据」的智能应用的关键。
为什么需要工具调用? LLM 本身的短板是什么?
大模型的天生缺陷,也是工具存在的核心价值:
-
❌ LLM 是静态知识库 :训练数据截止到某个时间(比如 GPT3.5 截止 2024 年 7 月),无法获取实时信息(查今天的天气、股票价格、最新新闻);
-
❌ LLM 不擅长精准计算:算个复杂数学题、统计数据、汇率换算,很容易出错;
-
❌ LLM 没有操作能力:无法读写文件、调用 API 接口、操作数据库、发邮件、执行代码;
-
❌ LLM 是生成式模型:回答基于概率,而非事实,容易出现「幻觉」(一本正经说假话)。
工具的价值:弥补以上所有短板,让 LLM 成为「大脑」,工具成为「手脚」
LLM 负责「思考」→ 分析用户提问,判断「是否需要调用工具」「调用哪个工具」「传什么参数」Tools 负责「执行」→ 调用外部服务完成任务,返回精准结果LLM 再负责「总结」→ 把工具返回的结果,整理成自然语言回答用户
使用@tool装饰器创建工具
在定义工具的时候,需要写明函数名(工具名称),字符串文档(工具描述)和类型注解(工具参数) ,这些内容都是传给schema的。
所谓的schema ,就是对工具输入/输出的正式描述,让 LLM 和外部工具之间实现强类型、可预测的通信。告诉
LLM「工具需要哪些参数、每个参数的类型是什么、可选值有哪些」;
工具的名称只是告诉LLM有哪些工具,而工具的描述就是告诉LLM工具的能力,让LLM知道什么时候调用工具,工具参数就是告诉LLM如何进行调用。所以,在定义工具的时候,函数名,字符串文档和类型注解都不能少。
方式一:使用@tool定义工具,如果不写字符串文档,会报错。
from langchain_core.tools import tool
from typing_extensions import Annotated
#定义工具
@tool
def add(a:int,b:int) -> int:
"""两数相加
Args:
a: 第一个整数
b: 第二个整数
"""
return a+b
#对函数参数进行描述
#def add(
# a:Annotated[int,...,"第一个整数"],
# b:Annotated[int,...,"第二个整数"]
# ) -> int:
# """两数相加"""
# return a+b
print(add.invoke({"a": 2, "b": 3}))
print(add.name) #工具名称
print(add.description) #工具描述
print(add.args) #工具参数

方式二:在LangChain中,可以使用Pydantic类,提供运行时数据验证和类型检查。
from langchain_core.tools import tool, ArgsSchema
from pydantic import BaseModel,Field
class AddInput(BaseModel):
"""两数之和"""
a:int=Field(...,description="第一个参数")
b:int=Field(...,description="第二个参数")
@tool(args_schema=AddInput)
def add(a:int,b:int) -> int:
return a+b
print(add.invoke({"a": 2, "b": 3}))
print(add.name) #工具名称
print(add.description) #工具描述
print(add.args) #工具参数

使用StructuredTool类提供的函数创建工具
方式一:
from langchain_core.tools import StructuredTool
def add(a:int,b:int) -> int:
"""两数之和"""
return a+b
add_tool=StructuredTool.from_function(func=add)
print(add_tool.invoke({"a": 3, "b": 2}))
print(add_tool.name)
print(add_tool.description)
print(add_tool.args)

方式二:
def add(a:int,b:int) -> int:
return a+b
add_tool=StructuredTool.from_function(
func=add,
name="add_tool", #定义工具名称
description="两数之和" #工具描述
)
print(add_tool.invoke({"a": 3, "b": 2}))
print(add_tool.name)
print(add_tool.description)
print(add_tool.args)
如果想要对工具参数加上描述的话,可以使用pydantic
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
#使用pydantic完成对参数的描述
class AddInput(BaseModel):
a:int=Field(...,description="第一个参数")
b:int=Field(...,description="第二个参数")
def add(a:int,b:int) -> int:
return a+b
add_tool=StructuredTool.from_function(
func=add,
name="add_tool", #定义工具名称
description="两数之和", #工具描述
args_schema=AddInput
)
print(add_tool.invoke({"a": 3, "b": 2}))
print(add_tool.name)
print(add_tool.description)
print(add_tool.args)

2.2绑定工具
定义工具 ------> 绑定工具 ----> 调用工具。
在我们问LLM问题的时候,不一定会调用工具,而是根据输入的相关性决定是否调用工具,如果要强制调用工具,需要在绑定工具的时候设置一个参数(如下代码)
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from typing_extensions import Annotated
#创建工具
@tool
def add(
a:Annotated[int,...,"第一个整数"],
b:Annotated[int,...,"第二个整数"]
)->int:
"""两数相加"""
return a+b
#创建工具
@tool
def multipy(
a:Annotated[int,...,"第一个整数"],
b:Annotated[int,...,"第二个整数"]
)->int:
"""两数相乘"""
return a*b
#定义模型
model=ChatOpenAI(model="gpt-4o-mini")
#绑定工具
tools=[add,multipy]
model_with_tools=model.bind_tools(tools=tools)
#强制选择工具
#model_with_tools=model.bind_tools(tools=tools,tool_choice="any")
#定义消息列表
message=[
HumanMessage("2乘3等于多少?2加3等于多少?")
]
#返回值为AIMesaage类型,里面包含一个too_calls字段,是工具选择的结果
ai_msg=model_with_tools.invoke(message)
print(ai_msg)
# tool_calls=[
# {'name': 'multipy', 'args': {'a': 2, 'b': 3}, 'id': 'call_EU7fNYPaapu1KhBAH8F1lmUk', 'type': 'tool_call'},
# {'name': 'add', 'args': {'a': 2, 'b': 3}, 'id': 'call_2uPaccelb08r38a1fFJ4PCSE', 'type': 'tool_call'}
# ]
message.append(ai_msg)
#将AI返回的工具选择的结果加入到消息中
for tool_call in ai_msg.tool_calls:
selected_tool={"add":add,"multipy":multipy}[tool_call["name"].lower()]
tool_msg=selected_tool.invoke(tool_call) #返回类型为Too_Message,其中包含答案
message.append(tool_msg)
#现在已经知道了用户的消息,AI回复的消息,在加上系统消息
#将这些都发送给LLM,整合消息
print(model.invoke(message).content)

2.3LangChain提供的工具
工具也不是全部都需要我们自己受挫,其实 LangChain 官方也已经给我们提供了很多现成的工具(Tool)和⼯具包(Toolkit)。
LangChain 提供工具的见:https://docs.langchain.com/oss/python/integrations/providers/overview。
写好的工具一般都是为了使用LangChain 中集成的三方组件或工具而创造的,有搜索、数据库、网页浏览器等相关的工具。
比如搜索引擎工具:

示例:TavilySearch
TavilySearch 类可以支持我们进行搜索,Tavily 是一个专门为 AI 设计的搜索引擎,专为智能体检索与推理需求量身打造的工具。
Tavily 不仅提供了高度可编程的 API 接口,还具备显著优于传统搜索引擎的上下文相关性理解能力。能够以结构化、可解析的形式返回搜索结果,便于将检索到的信息直接用于后续的推理、生成或任务执行流程。
官网:https://www.tavily.com/,需先申请API KEY,然后添加到系统环境变量中。
代码示例:
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langchain_tavily import TavilySearch
#定义模型
model=ChatOpenAI(model="gpt-4o-mini")
#定义工具
tool=TavilySearch(max_results=4)
#绑定工具
model_with_rools=model.bind_tools([tool])
#定义消息
message=[
HumanMessage("今天北京的天气怎么样")
]
#AIMessage(获得选择工具的结果)
ai_msg=model_with_rools.invoke(message)
message.append(ai_msg)
#构建ToolMessage
for tool_call in ai_msg.tool_calls :
tool_msg=tool.invoke(tool_call) #由于我们这里只定义了一个工具,所以就不用构建字典了
message.append(tool_msg)
print(model.invoke(message).content)
3.聊天模型------结构化输出
在 LangChain 中,聊天模型提供了额外的功能:结构化输出。一种使聊天模型以结构化格式(例如JSON)进行响应的技术。
传统调用大模型,结果都是按照字符串格式返回的,例如下面的代码:
from langchain_openai import ChatOpenAI
model=ChatOpenAI(model="gpt-4o-mini")
print(model.invoke("请结合篮球和鸡,给我讲一个笑话").content)

这个字符串对人类很友好,但对程序不友好。如果我们想从这段文本中提取出 "公司名" 和 "股价变化" 并用在后续逻辑中,则需要编写复杂且容易出错的解析代码(例如,使用正则表达式)。
聊天模型的 with_structured_output方法则允许我们预先定义一个期望的数据结构,并要求大模型必须按照这个结构返回信息。
3.1with_structured_output
要想使用结构化输出的能力,LangChain提供了with_structured_output 方法,该方法需要先定义输出结构,然后执行通过with_structured_output()得到的 Runnable 实例 。
返回Pydantic对象
示例代码:
#调用大模型返回结构化对象
from typing import Optional
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
#定义模型
model=ChatOpenAI(model="gpt-4o-mini")
#定义结构化对象
class Joke(BaseModel):
setup: str=Field(description="这个笑话的主题") #setup:场景构建/前置条件
punchline: str=Field(description="这个笑话的妙处") #punchline:点睛之笔
rating: Optional[int]=Field(default=None,description="从1-10分,给这个笑话的评分") #rating:效果评分
#将返回结构绑定到模型
model_with_struct=model.with_structured_output(Joke)
#调用模型
print(model_with_struct.invoke("请结合篮球和鸡,给我讲一个笑话"))

同时也支持结构嵌套式的输出,示例代码:
#调用大模型返回结构化对象
from typing import Optional, List
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
#定义模型
model=ChatOpenAI(model="gpt-4o-mini")
#定义结构化对象
class Joke(BaseModel):
setup: str=Field(description="这个笑话的主题") #setup:场景构建/前置条件
punchline: str=Field(description="这个笑话的妙处") #punchline:点睛之笔
rating: Optional[int]=Field(default=None,description="从1-10分,给这个笑话的评分") #rating:效果评分
class Data(BaseModel):
jokes: List[Joke]
#将返回结构绑定到模型
model_with_struct=model.with_structured_output(Data)
#调用模型
print(model_with_struct.invoke("请结合篮球和鸡,给我讲两个笑话"))
输出结果:
jokes=[
Joke(setup='为什么篮球运动员喜欢吃鸡?', punchline='因为它们总是在场上引领风骚!', rating=8),
Joke(setup='一只鸡能打篮球吗?', punchline='当然可以,但它总是闯入三分线!', rating=7)
]
返回TypedDict
先了解一下TypedDict ,它用于为字典对象提供精确的、结构化的类型提示。它允许我们指定一个字典中应该有哪些键,以及每个键对应的值的类型。
最清晰、最常用的定义方式,就是类似于定义一个类(Python 3.8+)。
from typing import TypedDict
class User(TypedDict):
name: str
age: int
email: str
is_active: bool = True # 默认值
#对于它⾮常重要的⼀个能⼒就是捕捉键名拼写错误与类型错误。如:
user1: User = {
"name": "Bob",
"age": 25,
"email": "bob@example.com"
}
# 类型检查器会捕获这些错误
bad_user: User = {
"name": "Dave",
"age": "forty", # 错误:应该是int
"emial": "dave@example.com" # 错误:拼写错误
}
示例代码:
#调用大模型返回结构化对象------返回TypedDict结构
from typing import Optional
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict, Annotated
model=ChatOpenAI(model="gpt-4o-mini")
#TypedDict
class Joke(TypedDict):
"""给用户将一个笑话"""
setup: Annotated[str,...,"这个笑话的主题"]
punchline: Annotated[str,...,"这个笑话的妙处"]
rating: Annotated[Optional[int],...,"从1-10分,给这个笑话的评分"]
model_with_struct=model.with_structured_output(Joke)
print(model_with_struct.invoke("请结合篮球和鸡,给我讲两个笑话"))

返回JSON Schema
JSON Schema 是一套基于 JSON 格式的规范,用来:
-
定义 JSON 数据的「结构规则」(比如必须包含哪些字段、字段类型、取值范围);
-
校验 JSON 数据是否符合规则(比如评分必须是 1-10 的整数);
-
给字段加描述(比如
setup是笑话主题),让人和机器都能理解 JSON 结构。
示例:
{
"title": "Joke", // 模型名称
"description": "一条笑话的结构化数据", // 描述
"type": "object", // 数据类型是对象(字典)
"properties": { // 字段定义
"setup": {
"type": "string", // 字段类型:字符串
"description": "这个笑话的主题/铺垫", // 字段描述
"maxLength": 50 // 长度约束:最多50字
},
"punchline": {
"type": "string",
"description": "这个笑话的妙处/笑点",
"maxLength": 30
},
"rating": {
"type": "integer", // 字段类型:整数
"description": "1-10分的评分",
"minimum": 1, // 最小值:1
"maximum": 10, // 最大值:10
"nullable": True // 允许为空
}
},
"required": ["setup", "punchline"] // 必填字段:setup、punchline
}
示例代码:
from langchain_openai import ChatOpenAI
model=ChatOpenAI(model="gpt-4o-mini")
#定义JSON Schema
json_schema={
"title": "Joke", #模型名称
"description": "一条笑话的结构化数据", #描述
"type": "object", #数据类型是对象(字典)
"properties": { #字段定义
"setup": {
"type": "string", #字段类型:字符串
"description": "这个笑话的主题/铺垫", #字段描述
"maxLength": 50 #长度约束:最多50字
},
"punchline": {
"type": "string",
"description": "这个笑话的妙处/笑点",
"maxLength": 30
},
"rating": {
"type": "integer", #字段类型:整数
"description": "1-10分的评分",
"minimum": 1, #最小值:1
"maximum": 10, #最大值:10
"nullable": True #允许为空
}
},
"required": ["setup", "punchline"] #必填字段:setup、punchline
}
model_with_struct=model.with_structured_output(json_schema)
print(model_with_struct.invoke("请结合篮球和鸡,给我讲一个笑话"))

选择输出结构
在上述三种方法中,返回的结构都是按照我们定义好的结构进行的,此时如果我们询问的问题和定义的这个结构没有关系,还是会按照定义好的结构进行返回。如下场景:
from typing import Optional, List
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
#定义模型
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分,给这个笑话的评分")
#将返回结构绑定到模型
model_with_struct=model.with_structured_output(Joke)
#调用模型
print(model_with_struct.invoke("请结合篮球和鸡,给我讲一个笑话"))
print(model_with_struct.invoke("请介绍下你自己"))

而此时我们希望第二个问题,不按照这种结构进行返回,这时我们可以重新定义一个Pydantic对象,然后在调用的时候,让模型进行选择,选择应该输出为哪种结构。
from typing import Optional, List, Union
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
#定义模型
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 UserQuestion(BaseModel):
"""回复用户问题"""
content: str=Field(description="回复用户的问题")
#定义选择结构
class FinalResponse(BaseModel):
"""选择最终的输出结构"""
final_output: Union[Joke,UserQuestion]
#将返回结构绑定到模型
model_with_struct=model.with_structured_output(FinalResponse)
#调用模型
print(model_with_struct.invoke("请结合篮球和鸡,给我讲一个笑话"))
print(model_with_struct.invoke("请介绍下你自己"))

4.聊天模型------流式输出
核心定义:流式输出(Streaming)
-
传统输出(非流式):LLM 生成完所有内容后,一次性返回完整结果;
-
流式输出(Streaming) :LLM 生成内容的过程中,逐块返回数据。
核心优势:
-
降低用户等待感(长文本生成时不用等完全结束);
-
实时反馈(比如生成 10 条笑话时,生成一条展示一条);
-
节省内存(无需缓存完整结果,边生成边处理)。
4.1stream()同步输出
在 LangChain 聊天模型中,可以使用其 .stream() 方法,来同步生成流式响应的效果。聊天模型的 .stream() 方法返回一个迭代器,该迭代器在生成输出时同步产生输出消息块(chunk) 。可以使用for循环实时处理每个块。代码如下:
#流式输出
from langchain_openai import ChatOpenAI
model=ChatOpenAI(model="gpt-4o-mini")
#流式输出
#stream接口返回的结果是迭代器,产生消息块
for chunk in model.stream("解释下什么是LCEL"):
print(chunk.content,end="|",flush=True)
#非流式输出
# print(model.invoke("解释下什么是LCEL"))

4.2astream()异步输出
对于流式传输,通常我们可以选择异步调用。
#异步流式输出
import asyncio
from langchain_openai import ChatOpenAI
model=ChatOpenAI(model="gpt-4o-mini")
#定义协程
async def async_stream():
async for chunk in model.astream("解释下什么是LCEL"):
print(chunk.content, end="|", flush=True)
asyncio.run(async_stream())
4.3自定义流式输出解析器
上面我们演示了如何让聊天模型进行流式输出。若此时我们希望修改上一步的输出样式(一个字或两个字的输出),将输出改为一句话一句话的输出,同时保留流式处理功能。那么我们需要在链中使用生成器函数,即可完成自定义流式输出的能力。
还记得之前说过,聊天模型的 .stream() 方法返回的是一个迭代器,该迭代器在生成输出时同步产生输出消息块 。那么我们的将实现的这些生成器的签名应该是 Iterator[Input] ->Iterator[Output] 。或者对于异步生成器:AsyncIterator[Input] ->AsyncIterator[Output] 。
下面是句号分隔列表的自定义输出解析器的示例:
#对于流式输出结果进行解析
from typing import List, Iterator
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
#定义模型
model=ChatOpenAI(model="gpt-4o-mini")
#定义输出解析器(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 [buffer[:stop_index].strip()]
buffer=buffer[stop_index+1:]
yield [buffer.strip()]
#定义链
chain = model | parser | split_into_list
#获取流式输出结果
#stream的结果返回的是一个迭代器
for chunk in chain.stream("写一段关于爱情的歌词,需要5句话,每句话用中文句号分开"):
print(chunk,end="|",flush=True)
print("\n")

4.4深度探索流式输出
SSE协议介绍
HTTP 协议本身设计为无状态的请求-响应模式,严格来说,是无法做到服务器主动推送消息到客户端,但通过 Server-Sent Events (服务器发送事件,简称 SSE)技术可实现流式传输,允许服务器主动向浏览器推送数据流。
也就是说,服务器向客户端声明,接下来要发送的是流消息(streaming),这时客户端不会关闭连接,会一直等待服务器发送过来新的数据流。
SSE(Server-Sent Events)是一种基于 HTTP 的轻量级实时通信协议,浏览器可以通过内置的EventSource API 接收并处理这些实时事件。

核心特点
- 基于 HTTP 协议
复用标准 HTTP/HTTPS 协议,无需额外端口或协议,兼容性好且易于部署。
- 单向通信机制
SSE 仅支持服务器向客户端的单向数据推送,客户端通过普通 HTTP 请求简历连接后,服务器可持续发送数据流,但客户端无法通过同一连接向服务器发送数据。
- 自动重连机制
支持断线重连,连接中断时,浏览器会自动尝试重新连接(支持 retry 字段指定重连间隔)。
- 定义消息类型
客户端发起请求后,服务器保持连接开放,响应头设置 Content-Type: text/event-stream ,标识为事件流格式,持续推送事件流。
数据格式
服务端向浏览器发送 SSE 数据,需要设置必要的 HTTP 头信息:
Content-Type: text/event-stream;charset=utf-8
Connection: keep-alive
每一次发送的消息,由若干个 message 组成,每个 message 之间由 \n\n 分隔,每个 message 内部由若干行组成,每一行都是如下格式:
[field]: value\n
Field 可以取值为:
data[必需]:数据内容
event[非必需]:表示自定义的事件类型,默认是message事件
id[非必需]:数据标识符,相当于每一条数据的编号
retry[非必需]:指定浏览器重新发起连接的时间间隔
除此之外,还可以有冒号 : 开头的行,表示注释。
数据示例:
event: foo\n
data: a foo event\n\n
data: an unnamed event\n\n
event: end\n
data: a bar event\n\n
对比 WebSocket

5.使用LangSmith跟踪LangChain应用
使用LangChain 构建的许多应用程序,可能会包含多个步骤和多次的 LLM 调用。随着这些应用程序变得越来越复杂,作为开发者,我们能够检查链或代理内部到底发生了什么变得至关重要。最好的方法是使用LangSmith。
LangSmith 与框架无关,它可以与 langchain 和 langgraph 一起使用,也可以不使用。LangSmith 是一个用于帮助我们构建生产级 LLM 应用程序的平台,它将密切监控和评估我们的应用。
LangSmith 平台地址:https://smith.langchain.com/

要想让 LangSmith 跟踪 LLM 应用,第一步申请 LangSmith API Key,点击 Settings,就会跳转到"API Keys"设置页面,若没有跳转,可以在左侧 tab 栏中找到进入。

创建完成后,保存好你的 API Key。
接下来配置两个环境变量:
LANGSMITH_TRACING="true" LANGSMITH_API_KEY="你的 LangSmith API Key"
配置好后,就可以使用了,以下面的代码为例:
#对于流式输出结果进行解析
from typing import List, Iterator
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
#定义模型
model=ChatOpenAI(model="gpt-4o-mini")
#定义输出解析器(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 [buffer[:stop_index].strip()]
buffer=buffer[stop_index+1:]
yield [buffer.strip()]
#定义链
chain = model | parser | split_into_list
#获取流式输出结果
#stream的结果返回的是一个迭代器
for chunk in chain.stream("写一段关于爱情的歌词,需要5句话,每句话用中文句号分开"):
print(chunk,end="|",flush=True)
print("\n")
查看 LangSmith 平台,这将在 LangSmith 的默认跟踪项目中生成调用的跟踪。点击最新一次的调用追踪:

