Skip to main content

如何从工具中流式传输事件

如果您有调用聊天模型、检索器或其他可运行对象的工具,您可能希望访问这些可运行对象的内部事件或使用额外属性配置它们。本指南将向您展示如何正确手动传递参数,以便您可以使用 .streamEvents() 方法实现此目的。

兼容性

为了支持更广泛的 JavaScript 环境,基础 LangChain 包默认不会自动将配置传播到子可运行对象。这包括 .streamEvents() 所需的回调。这是您可能无法看到自定义可运行对象或工具发出事件的常见原因。

您需要手动将 RunnableConfig 对象传播到子可运行对象。有关手动传播配置的示例,请参见下面 RunnableLambda 的 bar 实现。

本指南还要求 @langchain/core>=0.2.16。

假设您有一个自定义工具,它调用了一个链,该链通过提示聊天模型仅返回 10 个单词,然后反转输出来压缩其输入。首先,以一种简单的方式定义它:

Pick your chat model:

Install dependencies

yarn add @langchain/groq 

Add environment variables

GROQ_API_KEY=your-api-key

Instantiate the model

import { ChatGroq } from "@langchain/groq";

const model = new ChatGroq({
model: "llama-3.3-70b-versatile",
temperature: 0
});
import { ChatAnthropic } from "@langchain/anthropic";
const model = new ChatAnthropic({
model: "claude-3-5-sonnet-20240620",
temperature: 0,
});
import { z } from "zod";
import { tool } from "@langchain/core/tools";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

const specialSummarizationTool = tool(
async (input) => {
const prompt = ChatPromptTemplate.fromTemplate(
"You are an expert writer. Summarize the following text in 10 words or less:\n\n{long_text}"
);
const reverse = (x: string) => {
return x.split("").reverse().join("");
};
const chain = prompt
.pipe(model)
.pipe(new StringOutputParser())
.pipe(reverse);
const summary = await chain.invoke({ long_text: input.long_text });
return summary;
},
{
name: "special_summarization_tool",
description: "A tool that summarizes input text using advanced techniques.",
schema: z.object({
long_text: z.string(),
}),
}
);

直接调用工具也能正常工作:

const LONG_TEXT = `
NARRATOR:
(Black screen with text; The sound of buzzing bees can be heard)
According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off the ground. The bee, of course, flies anyway because bees don't care what humans think is impossible.
BARRY BENSON:
(Barry is picking out a shirt)
Yellow, black. Yellow, black. Yellow, black. Yellow, black. Ooh, black and yellow! Let's shake it up a little.
JANET BENSON:
Barry! Breakfast is ready!
BARRY:
Coming! Hang on a second.`;

await specialSummarizationTool.invoke({ long_text: LONG_TEXT });
.yad noitaudarg rof tiftuo sesoohc yrraB ;scisyhp seifed eeB

但如果你想访问聊天模型的原始输出而非完整工具,你可以尝试使用 streamEvents() 方法,并查找 on_chat_model_end 事件。以下是其工作原理:

const stream = await specialSummarizationTool.streamEvents(
{ long_text: LONG_TEXT },
{ version: "v2" }
);

for await (const event of stream) {
if (event.event === "on_chat_model_end") {
// Never triggers!
console.log(event);
}
}

请注意,子运行中没有发出聊天模型事件!

这是因为上面的示例未将工具的配置对象传递到内部链中。要解决此问题,请重新定义你的工具,使其接收一个特殊参数,该参数的类型为 RunnableConfig(更多详细信息请参见此指南)。在执行内部链时,还需要将该参数传递进去:

const specialSummarizationToolWithConfig = tool(
async (input, config) => {
const prompt = ChatPromptTemplate.fromTemplate(
"You are an expert writer. Summarize the following text in 10 words or less:\n\n{long_text}"
);
const reverse = (x: string) => {
return x.split("").reverse().join("");
};
const chain = prompt
.pipe(model)
.pipe(new StringOutputParser())
.pipe(reverse);
// Pass the "config" object as an argument to any executed runnables
const summary = await chain.invoke({ long_text: input.long_text }, config);
return summary;
},
{
name: "special_summarization_tool",
description: "A tool that summarizes input text using advanced techniques.",
schema: z.object({
long_text: z.string(),
}),
}
);

现在使用你的新工具尝试与之前相同的 .streamEvents() 调用:

const stream = await specialSummarizationToolWithConfig.streamEvents(
{ long_text: LONG_TEXT },
{ version: "v2" }
);

for await (const event of stream) {
if (event.event === "on_chat_model_end") {
// Never triggers!
console.log(event);
}
}
{
event: 'on_chat_model_end',
data: {
output: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: 'Bee defies physics; Barry chooses outfit for graduation day.',
name: undefined,
additional_kwargs: [Object],
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: [Object]
},
input: { messages: [Array] }
},
run_id: '27ac7b2e-591c-4adc-89ec-64d96e233ec8',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}

太棒了!这次有一个事件被触发了。

对于流式传输,如果可能的话,.streamEvents() 会自动调用链中启用流式传输的内部可运行对象,因此如果你希望获得聊天模型生成的 token 流,只需筛选 on_chat_model_stream 事件即可,无需其他更改:

const stream = await specialSummarizationToolWithConfig.streamEvents(
{ long_text: LONG_TEXT },
{ version: "v2" }
);

for await (const event of stream) {
if (event.event === "on_chat_model_stream") {
// Never triggers!
console.log(event);
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: 'Bee',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: ' def',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: 'ies physics',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: ';',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: ' Barry',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: ' cho',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: 'oses outfit',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: ' for',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: ' graduation',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: ' day',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}
{
event: 'on_chat_model_stream',
data: {
chunk: AIMessageChunk {
lc_serializable: true,
lc_kwargs: [Object],
lc_namespace: [Array],
content: '.',
name: undefined,
additional_kwargs: {},
response_metadata: {},
id: undefined,
tool_calls: [],
invalid_tool_calls: [],
tool_call_chunks: [],
usage_metadata: undefined
}
},
run_id: '938c0469-83c6-4dbd-862e-cd73381165de',
name: 'ChatAnthropic',
tags: [ 'seq:step:2' ],
metadata: {
ls_provider: 'anthropic',
ls_model_name: 'claude-3-5-sonnet-20240620',
ls_model_type: 'chat',
ls_temperature: 0,
ls_max_tokens: 2048,
ls_stop: undefined
}
}

自动传递配置(高级)

如果你使用过 LangGraph,你可能已经注意到在嵌套调用中无需手动传递配置。这是因为 LangGraph 利用了一个名为 async_hooks 的 API,但在许多(但并非所有)环境中并不支持该功能。

如果需要,你可以通过运行以下代码来全局导入并启用 AsyncLocalStorage,以启用自动配置传递功能:

import { AsyncLocalStorageProviderSingleton } from "@langchain/core/singletons";
import { AsyncLocalStorage } from "async_hooks";

AsyncLocalStorageProviderSingleton.initializeGlobalInstance(
new AsyncLocalStorage()
);

下一步

现在你已经了解了如何在工具内部流式传输事件。接下来,请查看以下指南以了解更多关于使用工具的内容:

你还可以查看一些更具体的工具调用用法:


Was this page helpful?


You can also leave detailed feedback on GitHub.