LangChain-v0.2文档翻译:3.5、如何流式传输

如何流式传输

在基于大型语言模型(LLMs)的应用中,流式传输对于提升终端用户的响应性至关重要。
LangChain 中的重要原语,如聊天模型、输出解析器、提示、检索器和代理,都实现了 LangChain Runnable 接口。

流式传输方法:

该接口提供了两种通用的流式传输方法:

  1. 同步 stream 和异步 astream:这是流式传输的默认实现,用于传输链的最终输出
  2. 异步 astream_events 和异步 astream_log:这些提供了一种传输链的中间步骤最终输出的方法。

让我们看看这两种方法,并尝试理解如何使用它们。

使用 Stream

所有的 Runnable 对象都实现了一个名为 stream 的同步方法和一个名为 astream 的异步变体。这些方法旨在以块的形式流式传输最终输出,一旦有块可用就立即产生。

只有当程序中的所有步骤都知道如何处理输入流时,即一次处理一个输入块并产生相应的输出块,流式传输才是可能的。

处理的复杂性可以从简单任务(如发出由 LLM 生成的标记)到更具有挑战性的任务(如在完整的 JSON 完成之前流式传输 JSON 的部分)不等。

探索流式传输的最佳起点是 LLM 应用中的最重要组件——LLM 本身!

LLM 和聊天模型

大型语言模型及其聊天变体是 LLM 基础应用中的主要瓶颈。

大型语言模型可能需要几秒钟才能对查询生成完整的响应。这比应用程序对最终用户感觉响应迅速的**~200-300 毫秒**阈值要慢得多。

使应用程序感觉更响应的关键策略是显示中间进度;即,通过逐个标记地从模型流式传输输出。

我们将展示使用聊天模型进行流式传输的示例。

# 安装 langchain-openai 库
pip install -qU langchain-openai

# 导入 getpass 和 os 模块
import getpass
import os

# 设置环境变量 OPENAI_API_KEY
os.environ["OPENAI_API_KEY"] = getpass.getpass()

# 从 langchain_openai 导入 ChatOpenAI
from langchain_openai import ChatOpenAI

# 创建 ChatOpenAI 实例
model = ChatOpenAI(model="gpt-3.5-turbo-0125")

让我们从同步 stream API 开始:

# 创建一个空列表用于存储流式传输的块
chunks = []

# 使用模型的 stream 方法进行流式传输
for chunk in model.stream("what color is the sky?"):
    chunks.append(chunk)
    # 打印每个块的内容
    print(chunk.content, end="|", flush=True)

# 输出示例:
# The| sky| appears| blue| during| the| day|.

或者,如果你在异步环境中工作,你可能会考虑使用异步 astream API:

# 创建一个空列表用于存储流式传输的块
chunks = []

# 使用模型的 astream 方法进行异步流式传输
async for chunk in model.astream("what color is the sky?"):
    chunks.append(chunk)
    # 打印每个块的内容
    print(chunk.content, end="|", flush=True)

# 输出示例:
# The| sky| appears| blue| during| the| day|.

让我们检查其中一个块:

chunks[0]
# 输出块的示例:
# AIMessageChunk(content='The', id='run-b36bea64-5511-4d7a-b6a3-a07b3db0c8e7')

我们得到了一个名为 AIMessageChunk 的东西。这个块表示 AIMessage 的一部分。

消息块设计上是累加的——简单地将它们相加就可以得到到目前为止的响应状态!

# 将块相加得到到目前为止的响应状态
chunks[0] + chunks[1] + chunks[2] + chunks[3] + chunks[4]
# 输出示例:
# AIMessageChunk(content='The sky appears blue during', id='run-b36bea64-5511-4d7a-b6a3-a07b3db0c8e7')

几乎所有的 LLM 应用都涉及不止一个调用语言模型的步骤。

让我们使用 LangChain Expression LanguageLCEL)构建一个简单的链,该链结合了提示、模型和解析器,并验证流式传输是否有效。

我们将使用 StrOutputParser 来解析模型的输出。这是一个简单的解析器,它从 AIMessageChunk 中提取 content 字段,给我们提供了模型返回的 token

提示:
LCEL 是一种声明式的方式来指定一个“程序”,通过将不同的 LangChain 原语连接在一起。使用 LCEL 创建的链从自动实现的 streamastream 中受益,允许最终输出的流式传输。实际上,使用 LCEL 创建的链实现了整个标准 Runnable 接口。

# 从 langchain_core.output_parsers 导入 StrOutputParser
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

# 从模板创建 ChatPromptTemplate 实例
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")

# 创建 StrOutputParser 实例
parser = StrOutputParser()

# 使用管道符创建链
chain = prompt | model | parser

# 使用链的 astream 方法进行异步流式传输
async for chunk in chain.astream({"topic": "parrot"}):
    print(chunk, end="|", flush=True)

# 输出示例:
# Here|'s| a| joke| about| a| par|rot|:
# A man| goes| to| a| pet| shop| to| buy| a| par|rot|.
# The| shop| owner| shows| him| two| stunning| pa|rr|ots| with| beautiful| pl|um|age|.
# "There|'s| a| talking| par|rot| an|d a| non|-|talking| par|rot|,"| the| owner| says|.
# "The| talking| par|rot| costs| $|100|,| an|d the| non|-|talking| par|rot| is| $|20|."
# The| man| says|,| "I|'ll| take| the| non|-|talking| par|rot| at| $|20|."
# He| pays| an|d leaves| with| the| par|rot|.
# As| he|'s| walking| down| the| street|,| the| par|rot| looks| up| at| him|
# an|d says|,| "You| know|,| you| really| are| a| stupi|d man|!"
# The| man| is| stun|ne|d an|d looks| at| the| par|rot| in| dis|bel|ief|.
# The| par|rot| continues|,| "Yes|,| you| got| r|ippe|d off| big| time|!| I| can| talk|
# just| as| well| as| that| other| par|rot|,| an|d you| only| pai|d $|20| |for| me|!"

注意,即使我们在链的末尾使用了 parser,我们也得到了流式传输的输出。parser 独立地对每个流式传输的块进行操作。许多 LCEL 原语也支持这种转换样式的直通流式传输,这在构建应用程序时非常方便。

自定义函数可以设计为返回生成器,这些生成器能够操作流。

某些可运行的组件,如提示模板和聊天模型,不能处理单独的块,而是聚合所有先前的步骤。这样的可运行组件可能会中断流式传输过程。

提示:
LangChain 表达式语言允许你将链的构建与使用模式(例如,同步/异步,批处理/流式传输等)分开。如果这对你要构建的内容不相关,你也可以依靠标准的命令式编程方法,通过分别调用每个组件的 invokebatchstream,将结果分配给变量,然后根据需要在下游使用它们。

使用输入流工作

如果你想在生成时流式传输 JSON 输出怎么办?

如果你依赖 json.loads 来解析部分 JSON,由于部分 JSON 不是有效的 JSON,解析将会失败。

你可能会完全不知所措,并声称流式传输 JSON 是不可能的。

好吧,事实证明有一种方法可以做到——解析器需要在输入流上操作,并尝试将部分 JSON “自动完成” 到有效状态。

让我们看看这样一个解析器的实际工作,以理解这意味着什么。

# 从 langchain_core.output_parsers 导入 JsonOutputParser
from langchain_core.output_parsers import JsonOutputParser

# 由于旧版本的 Langchain 中的一个错误,JsonOutputParser 没有从某些模型流式传输结果
chain = (model | JsonOutputParser())

# 使用链的 astream 方法进行异步流式传输
async for text in chain.astream(
    "output a list of the countries france, spain and japan and their populations in JSON format. "
    'Use a dict with an outer key of "countries" which contains a list of countries. '
    "Each country should have the key `name` and `population`"):
    print(text, flush=True)

# 输出示例:
# {}{'countries': []}{'countries': [{}]}{'countries': [{'name': ''}]}...
# {'countries': [{'name': 'France', 'population': 67413000}, {'name': 'Spain', 'population': 47351567}, {'name': 'Japan', 'population': 125584000}]}

现在,让我们打破流式传输。我们将使用前面的示例,并在末尾添加一个提取函数,从最终确定的 JSON 中提取国家名称。

危险:
任何操作最终确定的输入而不是输入流的链中的步骤都可能通过 streamastream 破坏流式传输功能。

提示:
稍后,我们将讨论 astream_events API,该 API 即使链包含仅操作最终确定的输入的步骤,也会从中间步骤流式传输结果。这是一个流式传输事件的示例。

# 从 langchain_core.output_parsers 导入 JsonOutputParser
from langchain_core.output_parsers import JsonOutputParser

# 一个操作最终确定的输入而不是输入流的函数
# 这个函数破坏了流式传输
def _extract_country_names(inputs):
    ""“一个不操作输入流并破坏流式传输的函数。”""
    if not isinstance(inputs, dict):
        return ""

    if "countries" not in inputs:
        return ""

    countries = inputs["countries"]

    if not isinstance(countries, list):
        return ""

    country_names = [
        country.get("name") for country in countries if isinstance(country, dict)
    ]
    return country_names

# 使用 JsonOutputParser 和 _extract_country_names 创建链
chain = model | JsonOutputParser() | _extract_country_names

# 使用链的 astream 方法进行异步流式传输
async for text in chain.astream(
    "output a list of the countries france, spain and japan and their populations in JSON format. "
    'Use a dict with an outer key of "countries" which contains a list of countries. '
    "Each country should have the key `name` and `population`"):
    print(text, end="|", flush=True)

# 输出示例:
# ['France', 'Spain', 'Japan']
生成器函数

让我们使用一个可以操作输入流的生成器函数来修复流式传输。

提示:
使用 yield 的生成器函数允许编写操作输入流的代码。

# 从 langchain_core.output_parsers 导入 JsonOutputParser
from langchain_core.output_parsers import JsonOutputParser

# 定义一个操作输入流的异步生成器函数
async def _extract_country_names_streaming(input_stream):
    ""“定义一个操作输入流的异步函数。”""
    country_names_so_far = set()

    async for input in input_stream:
        if not isinstance(input, dict):
            continue

        if "countries" not in input:
            continue

        countries = input["countries"]

        if not isinstance(countries, list):
            continue

        for country in countries:
            name = country.get("name")
            if not name:
                continue
            if name not in country_names_so_far:
                yield name
                country_names_so_far.add(name)

# 使用 JsonOutputParser 和 _extract_country_names_streaming 创建链
chain = model | JsonOutputParser() | _extract_country_names_streaming

# 使用链的 astream 方法进行异步流式传输
async for text in chain.astream(
    "output a list of the countries france, spain and japan and their populations in JSON format. "
    'Use a dict with an outer key of "countries" which contains a list of countries. '
    "Each country should have the key `name` and `population`"):
    print(text, end="|", flush=True)

# 注意:
# 由于上述代码依赖于 JSON 自动完成,您可能会看到国家的部分名称(例如 `Sp` 和 `Spain`),这不是提取结果所期望的!
# 我们专注于流式传输概念,而不是链的结果。
France|Spain|Japan|

非流式组件

一些内置组件(如检索器)不提供任何 streaming。如果我们尝试对它们进行 stream,会发生什么呢?

# 从 langchain_community.vectorstores 导入 FAISS
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings

# 定义模板
template = """
Answer the question based only on the following context:
{context}

Question: {question}
"""

# 从模板创建 ChatPromptTemplate 实例
prompt = ChatPromptTemplate.from_template(template)

# 使用 FAISS 创建向量存储
vectorstore = FAISS.from_texts(
    ["harrison worked at kensho", "harrison likes spicy food"],
    embedding=OpenAIEmbeddings(),
)

# 将向量存储转换为检索器
retriever = vectorstore.as_retriever()

# 使用检索器的 stream 方法进行流式传输
chunks = [chunk for chunk in retriever.stream("where did harrison work?")]
chunks

# 输出示例:
# [[Document(page_content='harrison worked at kensho'),
#   Document(page_content='harrison likes spicy food')]]

流式传输只是从该组件产生了最终结果。

这没问题 🥹!并非所有组件都必须实现流式传输——在某些情况下,流式传输是不必要的、困难的或根本没有意义。

提示:
使用非流式组件构建的 LCEL 链,在许多情况下仍然可以流式传输,部分输出的流式传输从链中最后一个非流式步骤之后开始。

# 使用检索器、提示、模型和 StrOutputParser 创建检索链
retrieval_chain = (
    {
        "context": retriever.with_config(run_name="Docs"),
        "question": RunnablePassthrough(),
    }
    | prompt
    | model
    | StrOutputParser()
)

# 使用检索链的 stream 方法进行流式传输
for chunk in retrieval_chain.stream(
    "Where did harrison work? Write 3 made up sentences about this place."
):
    print(chunk, end="|", flush=True)

# 输出示例:
# Base|d on| the| given| context|,| Harrison| worke|d at| K|ens|ho|.
# Here| are| |3| |made| up| sentences| about| this| place|:
# 1|.| K|ens|ho| was| a| cutting|-|edge| technology| company| known| for| its| innovative| solutions| in| artificial| intelligence| an|d data| analytics|.
# 2|.| The| modern| office| space| at| K|ens|ho| feature|d open| floor| plans|,| collaborative| work|sp|aces|,| an|d a| vib|rant| atmosphere| that| fos|tere|d creativity| an|d team|work|.
# 3|.| With| its| prime| location| in| the| heart| of| the| city|,| K|ens|ho| attracte|d top| talent| from| aroun|d the| worl|d,| creating| a| diverse| an|d dynamic| work| environment|.

现在我们已经看到了 streamastream 的工作方式,让我们进入流式传输事件的世界。🏞️

使用流式传输事件

事件流式传输是一个测试版 API。根据反馈,这个 API 可能会有所变化。

注意:
本指南演示了 V2 API,并需要 langchain-core >= 0.2。对于与旧版 LangChain 兼容的 V1 API,请参考这里。

# 导入 langchain_core 并打印版本号
import langchain_core

langchain_core.__version__

为了使 astream_events API 正常工作:

  • 尽可能在整个代码中使用 async(例如,异步工具等)。
  • 如果定义自定义函数/可运行的,需要传播回调。
  • 当使用没有 LCEL 的可运行项时,请确保对 LLMs 调用 .astream() 而不是 .ainvoke,以强制 LLM 流式传输标记。
  • 如果有任何不符合预期的工作,请让我们知道!😃

事件参考

下面的参考表显示了各种可运行对象可能发出的一些事件。

注意:
当正确实现流式传输时,一个可运行的输入在输入流完全消耗之后才会被知道。这意味着 inputs 通常只会在 end 事件而不是 start 事件中包含。

事件名称分块输入输出
on_chat_model_start[模型名称]{“messages”: [[SystemMessage, HumanMessage]]}
on_chat_model_stream[模型名称]AIMessageChunk(content=“hello”)
on_chat_model_end[模型名称]{“messages”: [[SystemMessage, HumanMessage]]}AIMessageChunk(content=“hello world”)
on_llm_start[模型名称]{‘input’: ‘hello’}
on_llm_stream[模型名称]‘Hello’
on_llm_end[模型名称]‘Hello human!’
on_chain_startformat_docs
on_chain_streamformat_docs“hello world!, goodbye world!”
on_chain_endformat_docs[Document(…)]“hello world!, goodbye world!”
on_tool_startsome_tool{“x”: 1, “y”: “2”}
on_tool_endsome_tool{“x”: 1, “y”: “2”}
on_retriever_start[检索器名称]{“query”: “hello”}
on_retriever_end[检索器名称]{“query”: “hello”}[Document(…), …]
on_prompt_start[模板名称]{“question”: “hello”}
on_prompt_end[模板名称]{“question”: “hello”}ChatPromptValue(messages: [SystemMessage, …])

聊天模型

让我们从查看聊天模型产生的事件开始。

# 创建一个空列表用于存储事件
events = []

# 使用模型的 astream_events 方法进行异步流式传输事件
async for event in model.astream_events("hello", version="v2"):
    events.append(event)
# LangChainBetaWarning: 这个 API 正在测试版,将来可能会发生变化。
/home/eugene/src/langchain/libs/core/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: This API is in beta and may change in the future.
  warn_beta(

注意:
嘿,那个有趣的 version="v2" 参数是什么?!😾

这是一个测试版 API,我们几乎肯定会对它进行一些更改(事实上,我们已经做了!)

这个版本参数将允许我们最小化对您的代码的破坏性更改。

简而言之,我们现在烦扰您,是为了以后不烦扰您。

v2 仅在 langchain-core >= 0.2.0 可用。

让我们看看一些开始事件和一些结束事件。

events[:3]
# 输出示例:
# [{'event': 'on_chat_model_start', 'data': {'input': 'hello'}, 'name': 'ChatAnthropic', 'tags': [], 'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3', 'metadata': {}}, ...]
events[-2:]
# [{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='?', id='run-a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3')}, 'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3', 'name': 'ChatAnthropic', 'tags': [], 'metadata': {}}, 
# {'event': 'on_chat_model_end', 'data': {'output': AIMessageChunk(content='Hello! How can I assist you today?', id='run-a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3')}, 'run_id': 'a81e4c0f-fc36-4d33-93bc-1ac25b9bb2c3', 'name': 'ChatAnthropic', 'tags': [], 'metadata': {}}]

让我们重新审视解析流式传输 JSON 的示例链,探索流式传输事件 API。

# 由于旧版本的 Langchain 中的一个错误,JsonOutputParser 没有从某些模型流式传输结果
chain = (model | JsonOutputParser())

# 创建一个空列表用于存储事件
events = [
    event
    async for event in chain.astream_events(
        "output a list of the countries france, spain and japan and their populations in JSON format. "
        'Use a dict with an outer key of "countries" which contains a list of countries. '
        "Each country should have the key `name` and `population`",
        version="v2",
    )
]

如果你检查前几个事件,你会注意到有3个不同的开始事件,而不是2个开始事件。

这三个开始事件分别对应:

  1. 链(模型 + 解析器)
  2. 模型
  3. 解析器
events[:3]
# 输出示例:
# [{'event': 'on_chain_start', 'data': {'input': 'output a list of the countries france, spain and japan ...'}, 'name': 'RunnableSequence', 'tags': [], 'run_id': '4765006b-16e2-4b1d-a523-edd9fd64cb92', 'metadata': {}}, ...]

你认为如果你看最后3个事件会看到什么?中间的呢?

让我们使用这个 API 输出模型和解析器的流式传输事件。我们忽略了开始事件、结束事件和链的事件。

# 初始化事件计数器
num_events = 0

# 使用链的 astream_events 方法进行异步流式传输事件
async for event in chain.astream_events(
    "output a list of the countries france, spain and japan and their populations in JSON format. "
    'Use a dict with an outer key of "countries" which contains a list of countries. '
    "Each country should have the key `name` and `population`",
    version="v2",
):
    kind = event["event"]
    if kind == "on_chat_model_stream":
        # 打印聊天模型块的内容
        print(
            f"Chat model chunk: {repr(event['data']['chunk'].content)}",
            flush=True,
        )
    if kind == "on_parser_stream":
        # 打印解析器块的内容
        print(f"Parser chunk: {event['data']['chunk']}", flush=True)
    num_events += 1
    # 截断输出
    if num_events > 30:
        print("...")
        break

# 输出示例:
Chat model chunk: '{'
Parser chunk: {}
Chat model chunk: '\n  '
Chat model chunk: '"'
Chat model chunk: 'countries'
Chat model chunk: '":'
Chat model chunk: ' ['
Parser chunk: {'countries': []}
Chat model chunk: '\n    '
Chat model chunk: '{'
Parser chunk: {'countries': [{}]}
Chat model chunk: '\n      '
Chat model chunk: '"'
Chat model chunk: 'name'
Chat model chunk: '":'
Chat model chunk: ' "'
Parser chunk: {'countries': [{'name': ''}]}
Chat model chunk: 'France'
Parser chunk: {'countries': [{'name': 'France'}]}
Chat model chunk: '",'
Chat model chunk: '\n      '
Chat model chunk: '"'
Chat model chunk: 'population'
...
# ...

因为模型和解析器都支持流式传输,我们实时看到了两个组件的流式传输事件!这不是很酷吗?🦜

过滤事件

由于这个 API 产生了很多事件,能够过滤事件非常有用。

你可以按组件 name、组件 tags 或组件 type 进行过滤。

按名称
# 使用 with_config 设置模型和解析器的运行名称
chain = model.with_config({"run_name": "model"}) | JsonOutputParser().with_config(
    {"run_name": "my_parser"}
)

# 初始化事件计数器
max_events = 0

# 使用链的 astream_events 方法进行异步流式传输事件,并按名称过滤
async for event in chain.astream_events(
    "output a list of the countries france, spain and japan and their populations in JSON format. "
    'Use a dict with an outer key of "countries" which contains a list of countries. '
    "Each country should have the key `name` and `population`",
    version="v2",
    include_names=["my_parser"],
):
    print(event)
    max_events += 1
    # 截断输出
    if max_events > 10:
        print("...")
        break

# 输出示例:
# {'event': 'on_parser_start', 'data': {'input': 'output a list of the countries ...'}, 'name': 'my_parser', ...}
# ...
按类型
# 使用链的 astream_events 方法进行异步流式传输事件,并按类型过滤
async for event in chain.astream_events(
    'output a list of the countries france, spain and japan ...',
    version="v2",
    include_types=["chat_model"],
):
    print(event)
    max_events += 1
    # 截断输出
    if max_events > 10:
        print("...")
        break

# 输出示例:
# {'event': 'on_chat_model_start', 'data': {'input': 'output a list of the countries ...'}, 'name': 'model', ...}
# ...
按标签

注意:
标签是由给定可运行的子组件继承的。

如果您使用标签进行过滤,请确保这是您想要的。

# 使用 with_config 设置链的标签
chain = (model | JsonOutputParser()).with_config({"tags": ["my_chain"]})

# 初始化事件计数器
max_events = 0

# 使用链的 astream_events 方法进行异步流式传输事件,并按标签过滤
async for event in chain.astream_events(
    'output a list of the countries france, spain and japan ...',
    version="v2",
    include_tags=["my_chain"],
):
    print(event)
    max_events += 1
    # 截断输出
    if max_events > 10:
        print("...")
        break

# 输出示例:
# {'event': 'on_chain_start', 'data': {'input': 'output a list of the countries ...'}, 'name': 'RunnableSequence', 'tags': ['my_chain'], ...}
# ...

非流式组件

记住,有些组件因为不操作输入流而不能很好地进行流式传输,对吧?

虽然这样的组件在使用 astream 时可能会破坏最终输出的流式传输,但 astream_events 仍然会从支持流式传输的中间步骤产生流式传输事件!

# 定义一个不支持流式传输的函数。它操作最终确定的输入而不是操作输入流。
def _extract_country_names(inputs):
    if not isinstance(inputs, dict):
        return ""

    if "countries" not in inputs:
        return ""

    countries = inputs["countries"]

    if not isinstance(countries, list):
        return ""

    country_names = [
        country.get("name") for country in countries if isinstance(country, dict)
    ]
    return country_names

# 使用模型、JsonOutputParser 和 _extract_country_names 创建链
chain = (model | JsonOutputParser() | _extract_country_names)

# 由于 _extract_country_names 不操作流,astream API 无法正确工作
async for chunk in chain.astream(
    "output a list of the countries france, spain and japan ...",
):
    print(chunk, flush=True)

# 输出示例:
# ['France', 'Spain', 'Japan']

现在,让我们确认使用 astream_events 我们仍然可以看到模型和解析器的流式传输输出。

# 初始化事件计数器
num_events = 0

# 使用链的 astream_events 方法进行异步流式传输事件
async for event in chain.astream_events(
    "output a list of the countries france, spain and japan ...",
    version="v2",
):
    kind = event["event"]
    if kind == "on_chat_model_stream":
        print(
            f"Chat model chunk: {repr(event['data']['chunk'].content)}",
            flush=True,
        )
    if kind == "on_parser_stream":
        print(f"Parser chunk: {event['data']['chunk']}", flush=True)
    num_events += 1
    # 截断输出
    if num_events > 30:
        print("...")
        break

# 输出示例:
# Chat model chunk: '{'
# Parser chunk: {}
# ...

传播回调

注意:
如果你在工具中调用可运行的,你需要将回调传播到可运行的;否则,将不会产生流式传输事件。

注意:
当使用 RunnableLambdas@chain 装饰器时,回调会自动传播。

# 从 langchain_core.runnables 导入 RunnableLambda
from langchain_core.runnables import RunnableLambda
from langchain_core.tools import tool

# 定义一个 reverse_word 函数并使用 RunnableLambda 包装
def reverse_word(word: str):
    return word[::-1]

reverse_word = RunnableLambda(reverse_word)

# 定义一个不传播回调的自定义工具
@tool
def bad_tool(word: str):
    ""“自定义工具,不传播回调。”""
    return reverse_word.invoke(word)

# 使用 bad_tool 的 astream_events 方法进行异步流式传输事件
async for event in bad_tool.astream_events("hello", version="v2"):
    print(event)

# 输出示例:
# {'event': 'on_tool_start', 'data': {'input': 'hello'}, 'name': 'bad_tool', ...}
# ...

这里有一个正确传播回调的重新实现。你会注意到现在我们也得到了来自 reverse_word 可运行的事件。

# 定义一个正确传播回调的自定义工具
@tool
def correct_tool(word: str, callbacks):
    ""“正确传播回调的工具。”""
    return reverse_word.invoke(word, {"callbacks": callbacks})

# 使用 correct_tool 的 astream_events 方法进行异步流式传输事件
async for event in correct_tool.astream_events("hello", version="v2"):
    print(event)

# 输出示例:
# {'event': 'on_tool_start', 'data': {'input': 'hello'}, 'name': 'correct_tool', ...}
# ...

如果你在 Runnable Lambdas 或 @chains 中调用可运行的,那么回调将自动代表您传递。

# 使用 RunnableLambda 定义一个 reverse_and_double 异步函数
async def reverse_and_double(word: str):
    return await reverse_word.ainvoke(word) * 2

reverse_and_double = RunnableLambda(reverse_and_double)

# 调用 reverse_and_double 函数
await reverse_and_double.ainvoke("1234")

# 使用 reverse_and_double 的 astream_events 方法进行异步流式传输事件
async for event in reverse_and_double.astream_events("1234", version="v2"):
    print(event)

# 输出示例:
# {'event': 'on_chain_start', 'data': {'input': '1234'}, 'name': 'reverse_and_double', ...}
# ...

使用 @chain 装饰器:

# 从 langchain_core.runnables 导入 chain 装饰器
from langchain_core.runnables import chain

# 使用 chain 装饰器定义一个 reverse_and_double 异步函数
@chain
async def reverse_and_double(word: str):
    return await reverse_word.ainvoke(word) * 2

# 调用 reverse_and_double 函数
await reverse_and_double.ainvoke("1234")

# 使用 reverse_and_double 的 astream_events 方法进行异步流式传输事件
async for event in reverse_and_double.astream_events("1234", version="v2"):
    print(event)

# 输出示例:
# {'event': 'on_chain_start', 'data': {'input': '1234'}, 'name': 'reverse_and_double', ...}
# ...

后续步骤

现在你已经了解了一些使用 LangChain 流式传输最终输出和内部步骤的方法。

要了解更多,请查看本节中的其他操作指南,或查看有关 LangChain 表达式语言的概念指南。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值