【LangChain】流式传输原理与 LangSmith 应用监控全解析


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言:
  • [一. stream () 同步流式传输](#一. stream () 同步流式传输)
    • [1.1 基础使用](#1.1 基础使用)
    • [1.2 AIMessageChunk 详解](#1.2 AIMessageChunk 详解)
  • [二. astream () 异步流式传输](#二. astream () 异步流式传输)
    • [2.1 异步编程基础](#2.1 异步编程基础)
    • [2.2 异步流式传输实现](#2.2 异步流式传输实现)
  • [三. 自定义流式输出格式](#三. 自定义流式输出格式)
    • [3.1 使用 StrOutputParser](#3.1 使用 StrOutputParser)
    • [3.2 按句子流式输出](#3.2 按句子流式输出)
  • [四. 深度探索流式传输原理](#四. 深度探索流式传输原理)
    • [4.1 SSE 协议详解](#4.1 SSE 协议详解)
    • [4.2 LangChain 流式传输流程分析](#4.2 LangChain 流式传输流程分析)
  • [五. 使用 LangSmith 跟踪 LLM 应用](#五. 使用 LangSmith 跟踪 LLM 应用)
    • [5.1 LangSmith 简介](#5.1 LangSmith 简介)
    • [5.2 配置 LangSmith](#5.2 配置 LangSmith)
    • [5.3 查看跟踪结果](#5.3 查看跟踪结果)
  • 结尾

前言:

在构建大语言模型(LLM)应用时,用户体验是决定产品成败的关键因素之一。传统的 invoke() 调用方式会等待模型生成完整响应后才一次性返回结果,当生成长文本时,用户会面临长时间的空白等待,这严重影响了交互体验。流式传输(Streaming)技术正是为了解决这个问题而生的。它允许模型在生成内容的过程中逐块返回结果,让用户能够实时看到输出,就像真人在打字一样。本文将深入解析 LangChain 中流式传输的实现原理,从同步流式到异步流式,从基础使用到底层协议分析,并介绍如何使用 LangSmith 这一强大工具来监控和调试你的 LLM 应用。


一. stream () 同步流式传输

1.1 基础使用

LangChain 的聊天模型实现了标准的 Runnable 接口,因此天然支持流式传输。最简单的流式传输方式是使用 .stream() 方法,它返回一个迭代器,每次产生一个 AIMessageChunk 对象。

python 复制代码
from langchain_openai import ChatOpenAI

# 初始化大模型
model = ChatOpenAI(model="gpt-4o-mini")

# 流式输出
for chunk in model.stream("写一段关于春天的作文,1000字"):
    print(chunk.content, end="", flush=True)

运行这段代码,你会看到文本逐字逐句地显示在屏幕上,而不是等待几秒钟后一次性出现。

1.2 AIMessageChunk 详解

.stream() 方法返回的每个元素都是一个 AIMessageChunk 对象,它代表了 AIMessage 的一部分。AIMessageChunk 有一个非常重要的特性:可以直接相加

python 复制代码
chunks = []
for chunk in model.stream("写一段关于春天的作文,1000字"):
    chunks.append(chunk)

# 将所有消息块相加,得到完整的消息
full_message = chunks[0]
for chunk in chunks[1:]:
    full_message += chunk

print(full_message.content)

相加后的结果仍然是一个 AIMessageChunk 对象,它包含了所有块的内容和元数据。这个特性在我们需要缓存完整响应或进行后续处理时非常有用。


二. astream () 异步流式传输

2.1 异步编程基础

在讲解异步流式传输之前,我们先通过一个生活中的例子来理解同步和异步的区别。

想象你需要煮一壶水,同时还要给朋友发一条短信:

  • 同步方式:先花 5 分钟煮水,水开了再花 2 分钟发短信,总共耗时 7 分钟。在煮水的 5 分钟里,你什么也做不了,只能干等着。
  • 异步方式:开始煮水后,立刻去发短信,2 分钟短信发完了,再等 3 分钟水开,总共耗时 5 分钟。在煮水的等待时间里,你可以去做其他事情。

在 Python 中,我们使用 asyncio 库来实现异步编程,核心概念包括:

  • 协程(Coroutine) :使用 async def 定义的函数,可以在执行过程中暂停并稍后恢复。
  • await:用于暂停协程的执行,等待异步操作完成。
  • 事件循环(Event Loop):负责调度和管理协程的执行。
python 复制代码
import asyncio

# 定义协程
async def boil_water_async():
    print("开始烧水...")
    await asyncio.sleep(5)  # 模拟烧水5秒
    print("水开了!")

async def send_message_async():
    print("开始发短信...")
    await asyncio.sleep(2)  # 模拟发短信2秒
    print("短信发送成功!")

async def main():
    # 创建任务并交给事件循环调度
    task1 = asyncio.create_task(boil_water_async())
    task2 = asyncio.create_task(send_message_async())
    
    # 等待所有任务完成
    await task1
    await task2

# 运行主协程
asyncio.run(main())

运行结果:

Plain 复制代码
开始烧水...
开始发短信...
短信发送成功!
水开了!

可以看到,总耗时只有 5 秒,而不是 7 秒,这就是异步编程的优势。

2.2 异步流式传输实现

LangChain 提供了 .astream() 方法来支持异步流式传输,这在构建 Web 服务(如 FastAPI)时特别有用,可以避免阻塞服务器的事件循环。

python 复制代码
from langchain_openai import ChatOpenAI
import asyncio

model = ChatOpenAI(model="gpt-4o-mini")

async def async_stream():
    print("=== 异步调用 ===")
    async for chunk in model.astream("写一段关于春天的作文,1000字"):
        print(chunk.content, end="", flush=True)

asyncio.run(async_stream())

三. 自定义流式输出格式

3.1 使用 StrOutputParser

默认情况下,.stream() 方法返回的是 AIMessageChunk 对象,我们通常只需要其中的文本内容。这时可以使用 StrOutputParser 来提取文本内容。

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

输出结果:

Plain 复制代码
|在|星|光|下|我|们|相|拥|,|心|跳|如|歌|轻|轻|动|;|
|岁|月|如|流|水|般|匆|匆|,|爱|意|却|在|时|光|中|永|恒|。|
|你|的|笑|容|是|我|梦|中|,|温|暖|我|每|一|个|孤|单|的|瞬|间|;|
|就|算|风|雨|再|大|,|也|愿|与|君|同|舟|共|渡|;|
|只|愿|携|手|一|生|,|共|享|这|无|尽|的|爱|。|||

3.2 按句子流式输出

有时候我们不希望逐字输出,而是希望一句话一句话地输出。这可以通过自定义生成器来实现。

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from typing import Iterator, List

model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()

# 自定义生成器:按句号分割文本
def split_into_sentences(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:]
    # 处理缓冲区中剩余的内容
    if buffer.strip():
        yield [buffer.strip()]

# 构建链
chain = model | parser | split_into_sentences

# 流式输出
for chunk in chain.stream("写一段关于爱情的歌词,需要5句话,每句话用中文句号隔开。"):
    print(chunk, end="\n", flush=True)

输出结果:

Plain 复制代码
['在夜空下与你相拥,星光闪烁如我们的梦。']
['心跳声如同旋律,伴随风儿轻轻送。']
['每一次温柔的呼唤,都是对你深深的承诺。']
['时间静止在这一刻,爱在彼此眼中流动。']
['无论未来多么遥远,牵手同行是我唯一的愿望。']

四. 深度探索流式传输原理

4.1 SSE 协议详解

你可能会好奇,流式传输底层是如何实现的?为什么服务器可以持续不断地向客户端发送数据?答案就是 SSE(Server-Sent Events,服务器发送事件) 协议。

SSE 是一种基于 HTTP 的轻量级实时通信协议,它允许服务器主动向客户端推送数据流。与 WebSocket 不同,SSE 是单向通信,只能由服务器向客户端发送数据。

特性 SSE WebSocket
协议基础 HTTP 独立的 WebSocket 协议
通信方向 单向(服务器→客户端) 双向
连接管理 自动重连 需要手动管理
数据格式 文本 文本和二进制
使用场景 实时通知、流式输出 聊天、游戏、实时协作

在 LLM 流式传输场景下,SSE 是最合适的选择,因为我们只需要服务器向客户端推送生成的文本,不需要客户端向服务器发送额外的数据。

SSE 数据格式

SSE 的数据格式非常简单,每个消息由若干行组成,每行的格式为 [field]: value\n,消息之间用 \n\n 分隔。

Plain 复制代码
data: 你好\n\n
data: 我是\n
data: 一个AI助手\n\n
event: end\n
data: 对话结束\n\n

常用的字段包括:

  • data:必需,数据内容
  • event:可选,自定义事件类型
  • id:可选,数据标识符
  • retry:可选,指定浏览器重新发起连接的时间间隔

4.2 LangChain 流式传输流程分析

LangChain 本身并不提供底层的网络传输协议,它只是封装了大模型供应商提供的 SDK。以 OpenAI 为例,LangChain 的流式传输流程如下:

  1. 发起请求 :LangChain 调用 OpenAI SDK 的 chat.completions.create() 方法,并设置 stream=True
  2. 建立连接 :OpenAI SDK 向 https://api.openai.com/v1/chat/completions 发送 HTTP 请求,请求头中包含 Accept: text/event-stream
  3. 接收响应:OpenAI 服务器以 SSE 格式持续返回数据块。
  4. 转换格式 :LangChain 将 OpenAI 返回的原始数据块转换为 AIMessageChunk 对象。
  5. 返回结果 :通过迭代器将 AIMessageChunk 对象逐个返回给用户。

源码解析:_stream () 方法

BaseChatOpenAI 类中的 _stream() 方法是流式传输的核心实现:

python 复制代码
def _stream(
    self,
    messages: list[BaseMessage],
    stop: Optional[list[str]] = None,
    run_manager: Optional[CallbackManagerForLLMRun] = None,
    **kwargs: Any,
) -> Iterator[ChatGenerationChunk]:
    # 1. 强制启用流式模式
    kwargs["stream"] = True
    
    # 2. 构建请求 payload
    payload = self._get_request_payload(messages, stop=stop, **kwargs)
    
    # 3. 发起调用
    response = self.client.create(**payload)
    
    # 4. 处理响应
    with response as response:
        for chunk in response:
            # 将 OpenAI 数据块转换为 AIMessageChunk
            generation_chunk = self._convert_chunk_to_generation_chunk(
                chunk,
                AIMessageChunk,
                {}
            )
            
            if generation_chunk is None:
                continue
            
            # 触发回调
            if run_manager:
                run_manager.on_llm_new_token(
                    generation_chunk.text,
                    chunk=generation_chunk,
                )
            
            # 产出生成块
            yield generation_chunk

源码解析:_convert_chunk_to_generation_chunk () 方法

这个方法负责将 OpenAI 返回的原始数据块转换为 LangChain 的 AIMessageChunk 对象:

python 复制代码
def _convert_chunk_to_generation_chunk(
    self,
    chunk: dict,
    default_chunk_class: type,
    base_generation_info: Optional[dict],
) -> Optional[ChatGenerationChunk]:
    # 提取选择项数据
    choices = chunk.get("choices", [])
    
    if not choices:
        return None
    
    choice = choices[0]
    if choice["delta"] is None:
        return None
    
    # 将 delta 格式转换为 AIMessageChunk
    message_chunk = _convert_delta_to_message_chunk(
        choice["delta"], default_chunk_class
    )
    
    # 构建生成块
    generation_chunk = ChatGenerationChunk(
        message=message_chunk,
        generation_info=base_generation_info
    )
    
    return generation_chunk

五. 使用 LangSmith 跟踪 LLM 应用

5.1 LangSmith 简介

随着 LLM 应用变得越来越复杂,包含多个步骤和多次 LLM 调用,调试和监控变得越来越困难。LangSmith 是 LangChain 官方推出的一个用于构建生产级 LLM 应用的平台,它提供了强大的跟踪、监控和评估功能。

LangSmith 的主要特点:

  • 框架无关:可以与 LangChain、LangGraph 一起使用,也可以单独使用
  • 全链路跟踪:记录应用中每一步的输入、输出和耗时
  • 可视化界面:以瀑布流形式展示调用流程
  • 评估功能:自动评估 LLM 应用的性能和准确性

5.2 配置 LangSmith

使用 LangSmith 非常简单,只需要配置两个环境变量即可,不需要修改任何代码。

  1. 申请 API Key

    1. 访问 LangSmith 官网 并注册账号
    2. 点击左侧的 "Settings",然后点击 "Create API Key"
    3. 保存生成的 API Key
  2. 配置环境变量 : 在系统环境变量中添加以下两个变量:
    1.

    Plain 复制代码
    LANGSMITH_TRACING="true"
    LANGSMITH_API_KEY="你的 LangSmith API Key"

5.3 查看跟踪结果

配置完成后,运行任何 LangChain 代码,LangSmith 都会自动跟踪调用过程。

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

model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
chain = model | parser

result = chain.invoke("写一段关于爱情的歌词,需要5句话")
print(result)

运行代码后,访问 LangSmith 平台,你会看到最新的跟踪记录。点击进入详情页,可以看到:

  • 瀑布流视图:展示调用的完整流程和每个步骤的耗时
  • 输入输出:查看每个步骤的输入和输出内容
  • 元数据:包括模型名称、token 使用量、响应时间等

结尾

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:流式传输是提升 LLM 应用用户体验的关键技术,它基于 SSE 协议实现,允许模型在生成内容的过程中逐块返回结果。LangChain 提供了简单易用的 API,支持同步和异步两种流式传输方式,并且允许我们自定义输出格式。LangSmith 则是调试和监控 LLM 应用的必备工具,它能够记录应用中每一步的详细信息,帮助我们快速定位问题和优化性能。在实际开发中,我们应该始终使用流式传输来提升用户体验,并利用 LangSmith 来监控应用的运行状态。这两个技术的结合,能够帮助我们构建出更加流畅、稳定和可靠的 LLM 应用。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
老徐聊GEO1 小时前
AI搜索流量转化率实测分享:我的案例与复盘
人工智能·python
十里春风_jzh1 小时前
打造自己的 AI 知识库
人工智能
一次旅行8 小时前
HyperTool:突破传统工具调用限制,让Agent更高效执行复杂任务
人工智能
陈天伟教授9 小时前
图解人工智能(58)人工智能应用-围棋国手
人工智能·语音识别·机器翻译
闻道参看9 小时前
2026年AI优质企业培训系统综合测评:合规管控/数据量化
人工智能
老毛肚9 小时前
jeecg-boot-base-core 02 day
javascript·python
yaoxin5211239 小时前
434. Java 日期时间 API - Period 基于日期的时间段
java·开发语言·python
老虾头9 小时前
科技贴近烟火:本地化 AI,赋能各行各业日常经营
人工智能
毒爪的小新9 小时前
Linux 环境极速部署 vLLM:从零搭建生产级大模型推理服务
linux·人工智能·ai·语言模型·vllm