LangChain之聊天模型核心能力(二)

3. 聊天模型--结构化输出

在 LangChain 中,聊天模型提供了额外的功能:结构化输出。一种使聊天模型以结构化格式(例如 JSON)进行响应的技术。

例如,可能希望将模型输出存储在数据库中,并确保输出符合数据库模式。 这种需求激发了结构化输出的概念,其中可以指示模型使用特定的输出结构进行响应。

核心是:从"字符串"到"对象"的范式转换。

聊天模型的 with_structured_output方法则允许我们预先定义一个期望的数据结构,并要求大 模型必须按照这个结构返回信息。

3.1 with_structured_output()

要想使用结构化输出能力,LangChain 提供了一种方法 .with_structured_output() ,该方法 需要先定义输出结构,然后执行通过 .with_structured_output() 得到的 Runnable 实例。步 骤如下(伪代码):

python 复制代码
#定义输出结构
schema={"foo":"bar"}
#绑定schema,也就是生成支持结构化返回的runnable实例
model_with_structure=model.with_structured_output(schema)
#执行
structured_output = model_with_structure.invoke(user_input)

这是获得结构化输出的最简单、最可靠的方法。

此方法将 输出结构 作为参数输入,返回一个类似 model 的 Runnable。输出结果,输出的不是字符串或消息 ,而是 输出与给定输出结构相对应的对象。

该 输出结构 可以指定为 TypedDict 类、JSON Schema 类, Runnable 将返回一个字典。指定为 Pydantic 类,则将返回一个 Pydantic 对 象。

3.1.1 返回 Pydantic 对象

设置执行 Runnable 后的输出结果指定为 Pydantic 类,这将返回一个 Pydantic 对象。

python 复制代码
from typing import Optional, List
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field
from sqlalchemy.sql.annotation import Annotated
from typing_extensions import TypedDict

model = ChatDeepSeek(
    model="deepseek-v4-flash",
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    }
)
#Pydantic对象
class Joke(BaseModel):
    """给用户讲的一个笑话"""
    setup:str=Field(description="这个笑话的开头")
    punchline:str=Field(description="这个笑话的妙语")
    rating:Optional[int]=Field(default=None,description="1到10分,给这个笑话打分")

class Data(BaseModel):
    """获取关于笑话的数据列表"""
    jokes:List[Joke]

model_with_structured=model.with_structured_output(Data)
print(model_with_structured.invoke("分别讲一个关于国内大殖子和台湾蛙蛙的笑话,用贴吧老哥的攻击力和语气"))

3.1.2 返回 TypedDict

TypedDict ,它用于为字典对象提供精确的、结构化的类型提示。 它允许我们指定一个 字典中应该有哪些键,以及每个键对应的值的类型。它非常重要的一个能力就是捕捉键名拼写错误与类型错误。

最清晰、最常用的定义方式,就是类似于定义一个类

python 复制代码
from typing import TypedDict
class User(TypedDict):
name: str
age: int
email: str
is_active: bool = True # 默认值

代码示例:

python 复制代码
from typing import Optional, List
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field
from sqlalchemy.sql.annotation import Annotated
from typing_extensions import TypedDict

model = ChatDeepSeek(
    model="deepseek-v4-flash",
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    }
)


#TypedDict
class Joke(TypedDict):
    steup:Annotated[str,...,"笑话的开头"]
    punchline:Annotated[str,...,"这个笑话的妙语"]
    rating: Annotated[int,None, "1到10分,给这个笑话打分"]

model_with_structured=model.with_structured_output(Joke)
print(model_with_structured.invoke("分别讲一个关于国内大殖子和台湾蛙蛙的笑话,用贴吧老哥的攻击力和语气"))

3.1.3 返回 JSON

还可以让聊天模型直接返回 JSON,只不过为了声明 JSON,我们需要定义 JSON Schema

python 复制代码
from typing import Optional, List
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field
from sqlalchemy.sql.annotation import Annotated
from typing_extensions import TypedDict

model = ChatDeepSeek(
    model="deepseek-v4-flash",
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    }
)

#JSON Schema
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"],
}

model_with_structured=model.with_structured_output(json_schema)
print(model_with_structured.invoke("分别讲一个关于国内大殖子和台湾蛙蛙的笑话,用贴吧老哥的攻击力和语气"))

3.1.4 选择输出格式

创建具有联合类型属性的父模式,以使用 Pydantic 为例(其他同理)

python 复制代码
from typing import Optional, List, Union
from langchain_deepseek import ChatDeepSeek
from pydantic import BaseModel, Field


model = ChatDeepSeek(
    model="deepseek-v4-flash",
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    }
)
# Pydantic对象
class Joke(BaseModel):
    """给用户讲的一个笑话"""
    setup:str=Field(description="这个笑话的开头")
    punchline:str=Field(description="这个笑话的妙语")
    rating:Optional[int]=Field(default=None,description="1到10分,给这个笑话打分")

class Response(BaseModel):
    """用正常对话的方式来回应"""
    content:str=Field(description="用于对用户查询的会话响应")
class FinalResponse(BaseModel):
    """最终回复,选择合适的输出结构"""
    final_output:Union[Joke,Response]

model_with_structured=model.with_structured_output(FinalResponse)
print(model_with_structured.invoke("分别讲一个关于国内大殖子和台湾蛙蛙的笑话,用贴吧老哥的攻击力和语气"))
print(model_with_structured.invoke("你是谁"))

3.2 实用场景

3.2.1 场景1:作为信息提取器

python 复制代码
from langchain_deepseek import ChatDeepSeek
from typing import Optional
from pydantic import BaseModel, Field
from langchain_core.messages import HumanMessage, SystemMessage

# 定义大模型
model =ChatDeepSeek(
    model="deepseek-v4-flash",
    extra_body={
        "thinking":{
            "type":"disabled"
        }
    }
)
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)

3.2.2 场景2:使用"少样本提示"来增强信息提取能力

3.2.3 场景3:与工具结合使用

注意,使用聊天模型原生的工具搭配结构化输出并不好用!!!这里只是了解下其能力。更好的用法 见LangGraph Agent 能力。

拆解能力,单独依次执行

当需要同时使用结构化输出和其他工具时,需要注意顺序,不要弄反:

  1. 首先绑定工具

  2. 其次添加结构化输出

python 复制代码
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langchain_tavily import TavilySearch
from pydantic import BaseModel, Field

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

# 定义工具
tool = TavilySearch(max_results=4)

# 绑定工具
model_with_tools = model.bind_tools([tool])

# 定义消息列表
messages = [
    HumanMessage("北京今天的天气怎么样?")
]
ai_message = model_with_tools.invoke(messages)
messages.append(ai_message)

for tool_call in ai_message.tool_calls:
    tool_message = tool.invoke(tool_call)
    messages.append(tool_message)

class SearchResult(BaseModel):
    """结构化搜索对象"""

    query: str = Field(description="搜索查询")
    findings: str = Field(description="查询结果摘要")

model_with_structured = model_with_tools.with_structured_output(SearchResult)
print(model_with_structured.invoke(messages))

4. 聊天模型--流式传输

流式处理对于使基于 LLM 的应用程序能够响应最终用户至关重要。其通过逐步显示输出,甚至在完整的响应准备就绪之前,流式传输可以显着改善用户体验。

之前直接使用invoke 的调用方式属于非流式传输 ,看到的现象是聊天模型直接返回全量内容 ,若
模型思考时间较长,则我们等待的时间就越长

我们使用的deepseek客户端就是属于流式输出。可以看到输出时间快了很多

对于 LangChain 的聊天模型来说,它同样支持流式返回。

4.1 stream() 同步传输

LangChain 聊天模型中的**.stream() 方法** ,来同步生成流式响应的效果,.stream() 方法返回一个迭代器,该迭代器在生成输出时同步产生输出 消息块 。可以 使用 for 循环实时处理每个块。

python 复制代码
from langchain_deepseek import ChatDeepSeek

model=ChatDeepSeek(model="deepseek-v4-flash")
#流式输出,返回一个迭代器,产生的消息块
chunks=[]
for chunk in model.stream("写一段关于龟兔赛跑的故事,20字左右"):
    chunks.append(chunk)
    #chunks:AIMessage
    print(chunk.content,end="|",flush=True)
#我们得到了一个叫做 AIMessageChunk 的东西,它代表 AIMessage 的一部分,也就是消息块。
#消息块还可以直接相加
print("\n")
temp_chunks=chunks[0]+chunks[1]+chunks[2]+chunks[3]+chunks[4]+chunks[5]
print(temp_chunks)

4.2 astream() 异步传输

想象一个场景:你需要煮一壶水,同时还要给朋友发一条短信。我们分别用同步(传统)和异步两种 方式来完成,以此对比并引入 协程 和 事件循环 的概念

• 同步(阻塞)方式:这就像是一个"死心眼"的人,做事必须一件一件来:

python 复制代码
import time

#同步io

def boil_water():
    print("开始烧水...")
    time.sleep(5)#模拟烧水5s,cpu完全空闲
    print("烧水完成...")

def send_message():
    print("开始发消息...")
    time.sleep(2)
    print("发消息完成...")

def main():
    boil_water()
    send_message()

#耗时七秒
main()

问题: 在 boil_water 函数等待的5秒里,CPU 完全空闲,但却不能去做 send_message 任务, 效率低下。

• 异步方式:我们请出 asyncio 、 协程 和 事件循环 。

什么是协程?

• 多进程通常利用的是多核 CPU 的优势,同时执行多个计算任务。每个进程有自己独立的内存管 理,所以不同进程之间要进行数据通信比较麻烦。

• 多线程是在一个 cpu 上创建多个子任务,当某一个子任务休息的时候其他任务接着执行。多线程 的控制是由 python 自己控制的。线程存在数据同步问题,所以要有锁机制。

• 协程的实现是在一个线程内实现的,相当于流水线作业。由于线程切换的消耗比较大,所以对于 并发编程,可以优先使用协程。

协程,作为一种轻量级的并发编程模型,可以被视为用户态的"轻量级线程"。

其核心优势在于**其调度完全由用户空间掌控,**避免了操作系统内核的频繁介入,从而显著降低了上 下文切换的开销。

在诸如网络数据刷新、资源加载、用户界面更新、以及 I/O 读写等场景下,如果并 发任务的计算量相对较小、对系统资源占用较低,则不必动用操作系统级别的线程。

协程的切换则由程序员和编程语言控制 ,程序员决定在何时暂停或恢复协程。协程是一个特殊的函 数,它可以在执行过程中暂停,并在稍后恢复执行 。它用 async def 定义,并在需要暂停的地方使 用 await

python 复制代码
#异步IO
import asyncio

#协程
async def boil_water():
    print("开始烧水...")
    await asyncio.sleep(5) #await表示等待这个操作完成,但期间可以做别的事
    print("烧水完成...")

#协程
async def send_message():
    print("开始发消息...")
    await asyncio.sleep(2)
    print("发消息完毕...")

#协程调度
#事件循环
async def main():
    #创建两个任务,并交给事件循环去调度
    #1、烧水,任务1
    task1=asyncio.create_task(boil_water())
    #2、发消息。任务2
    task2=asyncio.create_task(send_message())

    #等待两个任务完成
    await task1
    await task2

#run会创建一个事件循环
#5s
asyncio.run(main())

事件循环

事件循环是asyncio(python标准库中的模块,用于编写异步I/O操作的代码)的核心,可以把它当作一个总调度员

工作流程:

1、他维护一个任务列表

2、不断循环检查每个任务

若任务处于"等待I/O状态"(比如等水烧开..)就暂停他,立即去执行下一个已经就绪的任务;

若任务等待时间到了或者I.O操作完成了,事件循环就恢复执行该任务

python 复制代码
# 主程序(也是一个协程)
async def main():
# 创建两个任务,并交给事件循环去调度
task1 = asyncio.create_task(boil_water_async())
task2 = asyncio.create_task(send_message_async())
# 等待两个任务都完成
await task1
await task2
# 它负责创建事件循环,并将第一个协程(主程序)放入其中运行。
asyncio.run(main())

通过asyncio,可以在单线程中同时处理多个任务。**一个在单线程内调度和管理所有协程 的核心机制,就是事件循环。**它不停地检查哪些协程可以执行,哪些在等待。

总结

• 协程是 asyncio 的核心概念之一。他是一个特殊函数,可以在执行过程中暂停,稍后可以恢复

协程通过async def关键字定义。通过await关键字暂停执行,等待异步操作完成

使用asyncio.run()函数运行一个协程。他会创建一个事件循环,并运行指定的协程。事件循环是 asyncio 的核心组件,负责调度和执行协程。它不断地检查是否有任务需要执 行,并在任务完成后调用相应的回调函数。

使用.astream(),异步生成流式响应

使用 .astream() 方法,来异步生成流式响应的效果,这专为非阻塞工作流程而设计。可以在 异步代码中使用它来实现相同的实时流式处理行为。

python 复制代码
import asyncio
from langchain_deepseek import ChatDeepSeek

model=ChatDeepSeek(model="deepseek-v4-flash")

#异步流式输出
async def async_stream():
    print("====异步调用====")
    async for chunk in model.astream("写一段关于春天的故事。100字"):
        print(chunk.content,end="|",flush=True)

asyncio.run(async_stream())

4.3 使用 StrOutputParser 解析模型的输出

StrOutputParser 是 LangChain 中最基础的输出解析器

它的核心作用,是将聊天模型返回的复杂**AIMessage 对象,解析并提取为最纯净的字符串文本。**

LLM 返回的原始结果是一个包含大量元数据的对象(如令牌用量、模型名称、响应ID等)。在大多数应用场景下,我们只关心回复的文本内容。StrOutputParser 就是专门负责完成这个"过滤与提取"工作的组件。

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_deepseek import ChatDeepSeek

#组件1:聊天模型
model=ChatDeepSeek(model="deepseek-v4-flash")
#2、初始化解析器
parser=StrOutputParser()
chain=model | parser
result=chain.invoke("简单介绍deepseek-v4")
print(result)

流式输出的兼容由于构建出的链本身也是标准的 Runnable 对象,它完美地继承了流式处理能力。在流式场景下,解析器会自动将每一个流式消息块转换为对应的字符串片段。

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_deepseek import ChatDeepSeek

#组件1:聊天模型
model=ChatDeepSeek(model="deepseek-v4-flash")
#2、初始化解析器
parser=StrOutputParser()
chain=model | parser
for chunk in chain.stream("简单介绍Claude"):
    print(chunk,end="")

注意:

流式传输不是聊天模型的专属特性,而是所有遵循 Runnable 接口标准的组件所共有的"标准能力"

可以借助"USB接口标准"这个类比来理解:

  • 聊天模型、输出解析器、处理链等,都像是按照"Runnable"(USB标准)设计的不同设备(U盘、鼠标、键盘)。

  • 流式传输 .stream(),就像"即插即用"一样,是这个标准本身定义好的一项通用功能。

  • 因此,只要是 Runnable 的实例,原则上都可以调用 .stream() 方法,这是一种基于接口规范的、而不是具体类别的能力。

实践中需要注意的三点

  1. "流出"的数据类型各不相同

    虽然都能流式输出,但不同组件流出的"数据块"类型完全不同。例如:

    • 聊天模型 直接 .stream(),产出的是 AIMessageChunk 对象。

    • "模型 + 解析器"构成的链 对流式输出的每个 AIMessageChunk 做了处理,再 .stream(),产出的就是纯净的 str 字符串。

  2. 并非所有组件都支持流式

    即便实现了 Runnable 接口,部分组件因其工作性质,不提供流式处理(或流式无意义)。例如,检索器 (Retrievers) 是一次性返回全量检索结果的,过程无法拆分,因此对它调用 .stream() 也只会获得一次性返回的结果,没有逐令牌生成的过程。

  3. 链式调用是常态

    在实际开发中,几乎不会单独使用模型的原生流,绝大多数情况下,都是通过构建链(Chain)的方式,利用解析器将流式消息处理为最终想要的格式(如字符串)再进行流式输出。

4.4 自定义流式输出解析器

一个思维模式的转变 :从这一节开始,我们要明白,自定义流式解析器的任务,就是对流经的数据进行任意的加工和重组。它不再局限于提取内容,而是可以改变数据的形态。

StrOutputParser,它能帮我们把大模型返回的复杂消息块,变成纯净的字符串,并且在流式输出时,也是一个字一个字地往外蹦。

但这带来了一个新问题:如果我们不想看一个字一个字地蹦,而是想等模型"说"完一句话,再把这句完整的话显示出来,该怎么办?

这就引出了这一节的核心:自定义流式输出解析器 。它允许我们通过写一个生成器函数 ,来任意地改变流式输出的"节奏"和"样式"。在链中使用 生成器函数,即可完成自定义流式输出的能力。

聊天模型的 .stream() 方法返回的是一个迭代器,该迭代器在生成输出时同步产 生输出 消息块 。那么我们的将实现的这些生成器的签名应该是Iterator[Input] -> Iterator[Output] 。它规定了我们写的生成器函数,必须接收一个"流"(迭代器)作为输入,并且也返回一个"流"(迭代器)作为输出。这样就能完美地嵌入到 model | parser 这个管道里。或者对于异步生成器:AsyncIterator[Input] -> AsyncIterator[Output] 。

python 复制代码
from typing import Iterator, List
from langchain_core.output_parsers import StrOutputParser
from langchain_deepseek import ChatDeepSeek

#组件1:聊天模型
model=ChatDeepSeek(model="deepseek-v4-flash")
#组件2:输出解析器(str)
parser=StrOutputParser()
#自定义生成器
def split_into_list(input:Iterator[str])->Iterator[List[str]]:
    #创建一个空字符串变量buffer
    buffer=""
    for chunk in input:
        buffer += chunk
        #遇到句号需要刷新buffer
        while"。" in buffer:
            #找到句号的位置
            stop_index=buffer.index("。")
            #yield用于创造生成器
            #buffer[:stop_index]是切片,取开头到句号位置(stop_index)之前的所有字符
            #.strip() 去掉字符串前后的空白字符(空格、换行等)。
            yield [buffer[:stop_index].strip()]#外面套上 [ ... ] 表示把这个字符串放进一个列表里。
            #yield [buffer[:stop_index].strip()] ------ 这是生成器的关键。
            # yield 有点像 return,但不会结束函数,而是"返回一个值,然后暂停函数,下次调用时继续"。
            # 这里返回的是一个列表,列表中只有一个元素: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句话,每句话中文句号隔开"):
    #使用parser,结果就是str
    print(chunk,flush=True)

4.5 深度探索流式传输

我们需要从源码层面回答三个根本性问题:

  1. LangChain 请求 OpenAI 时,底层使用什么网络协议?

  2. LangChain 如何实现并支持流式传输?

  3. OpenAI 返回的原始数据块是何格式? LangChain 如何将其转换为标准的 AIMessageChunk 对象?

SSE 协议介绍

流式传输并非 LangChain 自身创造,其根基在于大模型服务商(如 OpenAI)提供的 SSE(Server-Sent Events,服务器发送事件) 支持。

可以将其理解为一种基于 HTTP 的"长连接热线" 。相比传统"一问一答"即关闭的短连接,SSE 建立连接后,服务器可持续向客户端单向推送数据流,直到传输结束。这为流式输出提供了底层物理通道。

HTTP 协议本身设计为无状态的请求-响应模式,严格来说,是无法做到服务器主动推送消息到客户端,但通过Server-Sent Events (服务器发送事件,简称 SSE)技术可实现流式传输,允许服

务器主动向浏览器推送数据流。

SSE(Server-Sent Events)是一种基于 HTTP 的轻量级实时通信协议,浏览器可以通过内置的

EventSource API 接收并处理这些实时事件。

核心特点

• 基于 HTTP 协议:复用标准 HTTP/HTTPS 协议,无需额外端口或协议,兼容性好且易于部署。

• 单向通信机制:SSE 仅支持服务器向客户端的单向数据推送,客户端通过普通 HTTP 请求建立连接后,服务器可持续发送数据流,但客户端无法通过同一连接向服务器发送数据。

• 自动重连机制:支持断线重连,连接中断时,浏览器会自动尝试重新连接(支持 retry 字段指定重连间隔)。

• 自定义消息类型:

客户端发起请求后,服务器保持连接开放。响应头设置Content-Type: text/eventstream,标识为事件流格式,持续推送事件流。

数据格式

服务端向浏览器发送 SSE 数据,需要设置必要的 HTTP 头信息:

python 复制代码
Content-Type: text/event-stream;charset=utf-8
Connection: keep-alive

LangChain 的流式处理流程(源码层面)

通过分析 BaseChatOpenAI 类中的 _stream 方法,可梳理出以下核心生命周期:

1. 发起请求(建立热线)

LangChain 本身并不"创造"或"规定"一个底层的网络传输协议,而是依赖于其底层的大模型供应商(如 OpenAI)的协议。因此当我们发起请求时,会在请求中设置 stream=True ( _stream() 源码中的第一步),表示OpenAI 服务器将在生成 Response 时向客户端发出数据(server-sent events,SSE)。此时 API会保持 HTTP 连接打开,并以特定格式发送数据。在构建 HTTP 请求时,LangChain 将参数 stream 强制设置为 True。此标志告知 OpenAI 服务器:本次对话需采用 SSE 模式,持续推送生成结果。

复制代码
# 源码逻辑
kwargs["stream"] = True 
payload = self._get_request_payload(messages, stop=stop, **kwargs)

2. 接入与接收(监听数据流)

LangChain 使用 openai 官方 SDK 构建的 HTTP 客户端(具体为 _SyncHttpxClientWrapper 类)发起调用,并持续监听来自 https://api.openai.com/v1 的 SSE 事件流。

3. 核心转换(数据"本土化")

这是最关键的一步。OpenAI 通过 SSE 推送的原始数据,是一系列包含 delta 字段的 JSON 对象(例如 "delta":{"content":"你"}),而非 LangChain 可直接使用的消息对象。

这一步的转换工作,由 _convert_chunk_to_generation_chunk 方法完成。它的职责是:

  • 输入 :OpenAI 推送的原始 JSON 数据块(chunk)。

  • 处理 :提取 choices[0].delta 中的内容。

  • 输出 :一个标准的 ChatGenerationChunk 对象,其核心属性 message 就是我们所熟悉的 AIMessageChunk 实例。

至此,每一块原始的流式数据便被封装成了包含 contentresponse_metadata 等完整信息的 AIMessageChunk 对象,可供下游组件统一处理。

数据转换结构对比

阶段 数据格式 示例内容
OpenAI 原始 SSE 数据块 JSON 对象 {"choices":[{"delta":{"content":"你"}}]}
LangChain 转换后 AIMessageChunk content='你', additional_kwargs={...}
总结
  • 技术基石:流式传输能力由模型服务商基于 SSE 协议提供,LangChain 是这一能力的使用者和封装者。

  • 核心机制 :LangChain 通过在请求中开启 stream=True 启用 SSE,并在客户端内部实现了一套从"原始 JSON 事件流"到"AIMessageChunk 对象序列"的完整转换逻辑。

  • 深入价值:掌握此流程,有助于开发者理解流式处理的全链路,便于日后进行定制化开发或排查相关问题。至此,我们从底层的网络协议,到上层的对象封装,完成了对流式传输的深度剖析。

LangChain流式传输的完整流程与底层协议。总结一下:

  1. langchain-openai 包通过集成 OpenAI Python SDK,提供了一个 HTTP 客户端。

  2. 因此,支持 LangChain 向 OpenAI 的 API 发起调用请求。

  3. 若希望发起流式传输请求,则需在请求中加入 stream=True ,向 OpenAI 说明以 SSE 协议进行

流式返回。

  1. LangChain 接收 OpenAI 的 SSE 格式的响应,并将其转换为 LangChain 自封装的消息格式,如

AIMessageChunk 消息。这样就可以以统一的方式处理来自不同模型提供商(OpenAI,

Anthropic等)的流式响应。

5. 使用 LangSmith 跟踪 LLM 应用

使用 LangSmith 时没有代码介入,只需要配置下环境,就可以直接监控我们的应用。

使用 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"

配置完成后,我们任意执行代码,查看 LangSmith 平台,这将在 LangSmith 的默认跟踪项目中生成调用的跟踪。跟踪会以瀑布流形式展示调用的完整步骤,以及每个步骤的详细信息和耗时。让我们能够检查内部到底发生了什么!

相关推荐
蔡俊锋1 小时前
AI 原生智能工作台
人工智能·需求分析·规格说明书·ai 原生智能工作台
AI前沿资讯1 小时前
2026年3D动画制作工具推荐:从传统工作流到AI一站式创作
人工智能·3d
littleM1 小时前
深度拆解 HermesAgent(六):研究功能与测试体系
开发语言·人工智能·python·架构·ai编程
隔壁大炮1 小时前
CNN图像分类案例
人工智能·pytorch·python·深度学习·算法·分类·cnn
Wanderer X1 小时前
熵、交叉熵、KL散度与Softmax
人工智能
量子炒饭大师1 小时前
【2026年全新 Images-2.0 使用教程】(附AI生图提示词+完整使用指南)
人工智能·chatgpt·ai生图·image 2.0
my1_1my1 小时前
AD-MT
人工智能·机器学习·机器翻译
littleM1 小时前
深度拆解 HermesAgent(七):CLI、安全与部署实践指南
人工智能·安全·架构
极智视界1 小时前
分类数据集 - 动物分类数据集下载
人工智能·yolo·数据集·图像分类·动物分类·算法训练