Langchain_v1.0|核心模块-core_component_07_streaming

Langchain_v1.0|核心模块-core_component_07_streaming

概述

流式传输代理运行的实时更新

LangChain 实施了一个流系统来展示实时更新。

流式传输对于增强基于LLM的应用程序的响应能力至关重要。通过逐步显示输出,即使在完整响应尚未准备好之前,流式传输也能显著改善用户体验(UX),特别是在处理LLM的延迟时。

Overview 概述

LangChain的流系统允许您从智能体运行中实时获取反馈并展示给您的应用程序。

LangChain流式处理的可能:

  • 流代理进度 --- 在每个代理步骤后获取状态更新。
  • 流式传输LLM令牌 --- 以生成时流式传输语言模型令牌。
  • 流式传输自定义更新 --- 发射用户定义的信号(例如,"Fetched 10/100 records")。
  • 流式传输多种模式 --- 选择 updates (代理进度), messages (LLM令牌+元数据), 或 custom (任意用户数据).

Supported Stream Modes 支持的流模式

将以下流模式之一或多个作为列表传递给stream或astream方法:

模式 描述
updates 每个代理步之后流式传输状态更新。如果在同一个步中进行了多次更新(例如,运行了多个节点),这些更新将分别流式传输。
messages 流式传输来自任何图节点的元组,其中调用了LLM。(token, metadata)
custom 使用流写入器从图节点内部流式传输自定义数据。

Agent Progress 代理进展

要流式传输代理进度,请使用 streamastream 方法与 stream_mode="updates"

这会在每个代理步骤之后发出一个事件

例如,如果您有一个代理程序调用一次工具,您应该看到以下更新:

  • LLM 节点:AIMessage 带有工具调用请求
  • 工具节点:ToolMessage 带有执行结果
  • LLM 节点:最终 AI 回复
python 复制代码
from langchain.agents import create_agent
from langchain_01.models.scii_deepseekv3 import model

def get_weather(city: str) -> str:
    """Get weather for a given city."""

    return f"It's always sunny in {city}!"

agent = create_agent(
    model,
    tools=[get_weather],
)
for chunk in agent.stream(  
    {"messages": [{"role": "user", "content": "What is the weather in SF?"}]},
    stream_mode="updates",
):
    for step, data in chunk.items():
        print(f"step: {step}")
        print(f"content: {data['messages'][-1].content_blocks}")
复制代码
step: model
content: [{'type': 'tool_call', 'name': 'get_weather', 'args': {'city': 'SF'}, 'id': '019beaf9bf2adb0e07b6a055ead0642e'}]
step: tools
content: [{'type': 'text', 'text': "It's always sunny in SF!"}]
step: model
content: [{'type': 'text', 'text': 'The weather in San Francisco is sunny!'}]

LLM Token

要将LLM生成的令牌实时流式传输,请使用stream_mode="messages"

下面你可以看到代理流式传输工具调用的输出和最终响应。

python 复制代码
from langchain.agents import create_agent 
def get_weather(city: str) -> str:
    """Get weather for a given city."""

    return f"It's always sunny in {city}!"

agent = create_agent(
    model,
    tools=[get_weather],
)
for token ,metadata in agent.stream(  
    {"messages": [{"role": "user", "content": "北京天气"}]},
    stream_mode="messages",
    config={"configurable": {"model":"MiniMaxAI/MiniMax-M2"}}
):
    # print(f"node: {metadata['langgraph_node']}")
    print(f"[{metadata['langgraph_node']}:{token.content}",end="|")
    # print("\n")
复制代码
[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:


|[model:|[model:|[model:|[model:|[model:|[model:|[tools:It's always sunny in 北京!|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:|[model:

|[model:北京|[model:今天|[model:是大|[model:晴天|[model:!|[model:|[model:|

定制更新

实时查看工具执行的更新,您可以使用get_stream_writer.

如果你在工具中添加get_stream_writer,则无法在LangGraph执行上下文之外调用该工具。

python 复制代码
from langgraph.config import get_stream_writer 
from langchain.tools import tool
import time

@tool
def get_weather(city: str) -> str:
    """Get weather for a given city."""
    # 模拟工具执行
    writer = get_stream_writer()
    writer(f"正在获取 {city} 的天气...")
    writer(f"Acquired data for city: {city}")
    # 假设工具执行需要 2 秒
    time.sleep(2)
    return f"It's always sunny in {city}!"


agent = create_agent(
    model,
    tools=[get_weather],
)

for chunk in agent.stream(
    {"messages":[{"role":"user","content":"北京天气"}]},
    config={"configurable": {"model":"MiniMaxAI/MiniMax-M2"}},
    stream_mode = "custom"
):
    # print(f"node: {metadata['langgraph_node']}")
    print(chunk)
复制代码
正在获取 北京 的天气...
Acquired data for city: 北京

流式传输多种模式

您可以通过将流模式传递为列表来指定多个流模式:stream_mode=["updates", "custom"].

流输出将是元组 (mode, chunk),其中 mode 是流模式的名称,chunk 是该模式流式传输的数据。

python 复制代码
from langchain.agents import create_agent
from langgraph.config import get_stream_writer


def get_weather(city: str) -> str:
    """Get weather for a given city."""
    writer = get_stream_writer()
    writer(f"Looking up data for city: {city}")
    writer(f"Acquired data for city: {city}")
    return f"It's always sunny in {city}!"

agent = create_agent(
    model,
    tools=[get_weather],
)

for stream_mode, chunk in agent.stream(  
    {"messages": [{"role": "user", "content": "What is the weather in SF?"}]},
    # 指定多个流模式
    stream_mode=["updates", "custom"]
):
    print(f"stream_mode: {stream_mode}")
    print(f"content: {chunk}")
    print("\n")
复制代码
stream_mode: updates
content: {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 281, 'total_tokens': 302, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'Qwen/Qwen3-Coder-30B-A3B-Instruct', 'system_fingerprint': '', 'id': '019beb09903dde67c0e53ae8dc64dce2', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019beb09-8e4d-7e52-a516-315ba7ab3c30-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'SF'}, 'id': '019beb0a2f69ab419ebd2b729827cf99', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 281, 'output_tokens': 21, 'total_tokens': 302, 'input_token_details': {}, 'output_token_details': {'reasoning': 0}})]}}


stream_mode: custom
content: Looking up data for city: SF


stream_mode: custom
content: Acquired data for city: SF


stream_mode: updates
content: {'tools': {'messages': [ToolMessage(content="It's always sunny in SF!", name='get_weather', id='dc9ad71e-4b8e-414d-b2cb-7ebb4c331284', tool_call_id='019beb0a2f69ab419ebd2b729827cf99')]}}


stream_mode: updates
content: {'model': {'messages': [AIMessage(content="The weather in SF is sunny! It seems like it's always a bright and cheerful day there.", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 323, 'total_tokens': 343, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'Qwen/Qwen3-Coder-30B-A3B-Instruct', 'system_fingerprint': '', 'id': '019beb0a301f3c27f37f4cec490fb7e6', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019beb0a-2e94-79e3-af32-c08128e2e6fd-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 323, 'output_tokens': 20, 'total_tokens': 343, 'input_token_details': {}, 'output_token_details': {'reasoning': 0}})]}}

常见模式

以下是展示流处理常见用例的示例。

流媒体工具调用

您可能希望同时流媒体播放两者:

  1. 部分JSON作为工具调用 生成
  2. 完成的、解析的工具调用执行情况

指定stream_mode="messages"将流式传输由代理中所有LLM调用生成的增量消息片段。要访问解析的工具调用的完成消息:.

如果这些消息状态 State被跟踪(如在create_agent模型节点中),使用stream_mode=["messages", "updates"]通过状态更新访问已完成的消息(如下所示)。

如果这些消息在该状态未被跟踪,请使用自定义更新或在流循环期间聚合块(下一节)。

python 复制代码
from typing import Any 

from langchain.agents import create_agent
from langchain.messages import AnyMessage, AIMessage,AIMessageChunk, AnyMessage,HumanMessage,ToolMessage,SystemMessage

def get_weather(city: str) -> str:
    """Get the weather for a city."""
    return f"The weather in {city} is sunny."

agent = create_agent(
    tools=[get_weather],
    model=model
)

def _render_message_chunk(token:AIMessageChunk) -> None:
    if token.content:
        print(token.text,end="|")
    if token.tool_call_chunks:
        print("Token Chunk",token.tool_call_chunks)

def _reander_completed_message(message:AnyMessage) -> None:
    if isinstance(message,AIMessage) and message.tool_calls:
        print(f"Tool calls  : {message.tool_calls}")
    if isinstance(message,ToolMessage):
        print(f"Tool message: {message.content_blocks}")

input = HumanMessage(content="What is the weather in Beijing?")
for stream_mode , data in agent.stream(
    {"messages":[input]},
    config={"configurable": {"model":"MiniMaxAI/MiniMax-M2"}},
    stream_mode=["messages", "updates"]
):
    if stream_mode == "messages":
        token,metadata = data 
        if isinstance(token,AIMessageChunk):
            _render_message_chunk(token)
    elif stream_mode == "updates":
        for source , update in data.items():
            if source in ("model","tools"):
                _reander_completed_message(update["messages"][-1])
复制代码
|Token Chunk [{'name': 'get_weather', 'args': '', 'id': '019beb259dc97d4ed09e5b455a7c180c', 'index': 0, 'type': 'tool_call_chunk'}]
Token Chunk [{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Token Chunk [{'name': None, 'args': '"city": "Beijing"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Token Chunk [{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Tool calls  : [{'name': 'get_weather', 'args': {'city': 'Beijing'}, 'id': '019beb259dc97d4ed09e5b455a7c180c', 'type': 'tool_call'}]
Tool message: [{'type': 'text', 'text': 'The weather in Beijing is sunny.'}]


|It's| sunny| in| Beijing|.|

访问已完成的消息

如果在代理的状态 State 中跟踪已完成的消息,您可以使用stream_mode=["messages", "updates"],如上所示,在流传输期间访问已完成的消息

在某些情况下,已完成的消息未反映状态更新中。如果您可以访问代理内部,您可以使用自定义更新在流期间访问这些消息。否则,您可以在流循环中聚合消息块(见下文)。

考虑下面的示例,其中我们将流写入器集成到简化的守护中间件中。此中间件演示了工具调用以生成结构化的"安全/不安全"评估(也可以使用结构化输出):

python 复制代码
# 在某些情况下,已完成的消息`未反映`在`状态更新`中。如果您可以访问代理内部,
# 您可以使用自定义更新在流期间访问这些消息
# 自定义流事件
from typing import Any,Literal

from langchain.agents import create_agent 
from langchain.agents.middleware import after_agent,AgentState
from langchain.messages import AIMessage
from langgraph.config import get_stream_writer 
from langgraph.runtime import Runtime
from pydantic import BaseModel,Field
from langchain_01.models.scii_deepseekv3 import model as  safe_model

class ResponseSafety(BaseModel):
    """评估响应的安全性"""
    evaluation: Literal["安全","不安全"]


@after_agent(can_jump_to=["end"])
def safety_evaluation(state: AgentState,runtime:Runtime) -> dict[str,Any] | None:
    """基于Model的模型评估响应的安全性的防护中间件"""
    stream_writer = get_stream_writer()
    
    # 获取响应
    messages = state["messages"]
    if not messages:
        return None
    last_message = messages[-1]
    if not isinstance(last_message,AIMessage):
        return None

    model_with_tools = safe_model.bind_tools([ResponseSafety],tool_choice="auto")
    result = model_with_tools.invoke(
        [
            SystemMessage(content="你是一个专业的安全评估模型,你的任务是评估用户的响应是否安全。"),
            HumanMessage(content=f"AI的响应是:{last_message.content}")
        ]
    )

    stream_writer(result)
    tool_call = result.tool_calls[0]
    if tool_call["args"]["evaluation"] == "不安全":
       last_message.content = "我不能提供该响应,请重新phrase您的请求。"

    return None

我们可以将这个中间件集成到我们的代理中,并包含其自定义的流事件。

python 复制代码
# 在某些情况下,已完成的消息`未反映`在`状态更新`中。如果您可以访问代理内部,
# 您可以使用自定义更新在流期间访问这些消息
# 自定义流事件
from typing import Any,Literal

def get_weather(city: str) -> str:
    """获取城市的天气"""
    return f"{city}的天气是晴朗的"

def _render_message_chunk(token: AIMessageChunk) -> None:
    """渲染消息块"""
    if token.text:
        print(token.text, end="|")
    if token.tool_call_chunks:
        print(token.tool_call_chunks)

def _render_completed_message(message: AnyMessage) -> None:
    """渲染完成的消息"""
    if isinstance(message,AIMessage) and message.tool_calls:
        print(f"工具调用:{message.tool_calls}")
    if isinstance(message, ToolMessage):
        print(f"工具消息: {message.content_blocks}")

agent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[safety_evaluation]
)

input_message = HumanMessage(content="北京的天气")

for stream_mode , data in agent.stream(
    {"messages": [input_message]},
    stream_mode=["messages","updates","custom"],
    config={"configurable":{"model":"deepseek-ai/DeepSeek-V3.2"}}
):
    if stream_mode == "messages":
        token , metadata = data
        if isinstance(token,AIMessageChunk):
            _render_message_chunk(token)
    if stream_mode == "updates":
        for source , update in data.items():
            if source in ("model","tools"):
                _render_completed_message(update["messages"][-1])
    if stream_mode == "custom":
        print(f"Tool Message : {data.tool_calls}")
复制代码
我来|帮|您|查询|北京的|天气|情况|。

|[{'name': 'get_weather', 'args': '', 'id': '019beb498d4c497624d8d61f203ec0e4', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"city": "北京"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
工具调用:[{'name': 'get_weather', 'args': {'city': '北京'}, 'id': '019beb498d4c497624d8d61f203ec0e4', 'type': 'tool_call'}]
工具消息: [{'type': 'text', 'text': '北京的天气是晴朗的'}]
根据|查询|结果|,|北京的|天气|是|晴朗|的|。|我需要|评估|这个|AI|响应|是否|安全|。|让我|分析|一下|:

|这个|响应|是关于|北京|天气|的|简单|信息|查询|结果|。|内容|只是|报告|天气|状况|为|"|晴朗|",|这是一个|中|性的|、|客观|的事实|陈述|。

|这种|类型的|响应|:
|1|.| |不|包含|任何|有害|、|危险|或|不当|内容|
|2|.| |不|涉及|敏感|话题|或个人|隐私|
|3|.| |不|包含|歧视|性|、|仇恨|性或|暴力|内容|
|4|.| |只是|提供|基本的|天气|信息|
|5|.| |没有任何|政治|敏感性|或|争议|性|

|因此|,|这个|响应|是完全|安全的|。

|[{'name': 'ResponseSafety', 'args': '', 'id': '019beb49c2a0c235bccdc5c77a60d8d6', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"evaluation": "安全"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Tool Message : [{'name': 'ResponseSafety', 'args': {'evaluation': '安全'}, 'id': '019beb49c2a0c235bccdc5c77a60d8d6', 'type': 'tool_call'}]

如果您不能添加自定义事件到流中,您可以在流循环中聚合消息块

python 复制代码
# 流 聚合消息块  =》 定义收集完整消息
input_message = {"role": "user", "content": "What is the weather in Boston?"}
full_message = None
for stream_mode, data in agent.stream(
    {"messages": [input_message]},
    stream_mode=["messages", "updates"],
):
    if stream_mode == "messages":
        token, metadata = data
        if isinstance(token, AIMessageChunk):
            _render_message_chunk(token)
            # 聚合消息块
            full_message = token if full_message is None else full_message + token  
            if token.chunk_position == "last":  
                if full_message.tool_calls:  
                    print(f"Tool calls: {full_message.tool_calls}") 
                full_message = None

    if stream_mode == "updates":
        for source, update in data.items():
            if source == "tools":
                _render_completed_message(update["messages"][-1])
复制代码
[{'name': 'get_weather', 'args': '', 'id': '019beb5171827dd1a2cec9473505512e', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"city": "Boston"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Tool calls: [{'name': 'get_weather', 'args': {'city': 'Boston'}, 'id': '019beb5171827dd1a2cec9473505512e', 'type': 'tool_call'}]
工具消息: [{'type': 'text', 'text': 'Boston的天气是晴朗的'}]
Boston|的|天气|是|晴|朗|的|。|[{'name': 'ResponseSafety', 'args': '', 'id': '019beb5179cfbb44aa3ef29cc083db21', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"evaluation": "\\u5b89\\u5168"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Tool calls: [{'name': 'ResponseSafety', 'args': {'evaluation': '安全'}, 'id': '019beb5179cfbb44aa3ef29cc083db21', 'type': 'tool_call'}]

带有人类参与的流媒体

Streaming with human-in-the-loop

To handle human-in-the-loop interrupts, we build on the above example:

我们在上面的示例基础上构建,以处理人类参与中断:

  • We configure the agent with human-in-the-loop middleware and a checkpointer 我们配置代理,使用人类参与中间件和检查点
  • We collect interrupts generated during the "updates" stream mode 我们在"更新"流模式下收集中断
  • We respond to those interrupts with a command
    我们用命令响应这些中断
python 复制代码
from langgraph.types import Command,Interrupt
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents.middleware import HumanInTheLoopMiddleware

def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"It's always sunny in {city}!"

# 定义记忆
memory = InMemorySaver()

# 定义代理
agent = create_agent(
    model,
    tools=[get_weather],
    # 定义中间件
    middleware=[  
        HumanInTheLoopMiddleware(interrupt_on={"get_weather": True}),  
    ],  
    checkpointer=memory,
)

def _render_interrupt(interrupt: Interrupt) -> None:  
    interrupts = interrupt.value  
    for request in interrupts["action_requests"]:  
        print(request["description"])  


input_message = {
    "role": "user",
    "content": (
        "Can you look up the weather in Boston and San Francisco?"
    ),
}

config = {"configurable":{"model":"MiniMaxAI/MiniMax-M2"},"thread_id": "1",}

interrupts = [] 

for stream_mode , data in agent.stream(
    {"messages": [input_message]},
    config=config,
    stream_mode=["messages", "updates"],
):
    if stream_mode == "messages":
        token, metadata = data
        if isinstance(token, AIMessageChunk):
            _render_message_chunk(token)
            # 聚合消息块
            full_message = token if full_message is None else full_message + token  
            if token.chunk_position == "last":  
                if full_message.tool_calls:  
                    print(f"Tool calls: {full_message.tool_calls}") 
                full_message = None
    if stream_mode == "updates":
        for source, update in data.items():
            if source == "tools":
                _render_completed_message(update["messages"][-1])
                interrupts.append(update)
                _render_interrupt(Interrupt(update))
            if source == "__interrupt__":  
                interrupts.extend(update)  
                _render_interrupt(update[0])  
复制代码
|[{'name': 'get_weather', 'args': '', 'id': '019beb5b039109b52c3f07bbd9ad6092', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"city": "Boston"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': 'get_weather', 'args': '', 'id': '019beb5b041f92dae22d611742bfd74b', 'index': 1, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 1, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"city": "San Francisco"', 'id': None, 'index': 1, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 1, 'type': 'tool_call_chunk'}]
Tool calls: [{'name': 'get_weather', 'args': {'city': 'Boston'}, 'id': '019beb5b039109b52c3f07bbd9ad6092', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'city': 'San Francisco'}, 'id': '019beb5b041f92dae22d611742bfd74b', 'type': 'tool_call'}]
Tool execution requires approval

Tool: get_weather
Args: {'city': 'Boston'}
Tool execution requires approval

Tool: get_weather
Args: {'city': 'San Francisco'}

We next collect a decision for each interrupt. Importantly, the order of decisions must match the order of actions we collected.

翻译:我们为每个中断收集一个决策。重要的是,决策的顺序必须与我们收集的操作顺序匹配一致。

To illustrate, we will edit one tool call and accept the other:

翻译:为了说明,我们将编辑一个工具调用并接受另一个:

python 复制代码
def _get_interrupt_decisions(interrupt: Interrupt) -> list[dict]:
    return [
        {
            "type": "edit",
            "edited_action": {
                "name": "get_weather",
                "args": {"city": "Boston, U.K."},
            },
        }
        if "boston" in request["description"].lower()
        else {"type": "approve"}
        for request in interrupt.value["action_requests"]
    ]

decisions = {}
for interrupt in interrupts:
    decisions[interrupt.id] = {
        "decisions": _get_interrupt_decisions(interrupt)
    }
decisions
复制代码
{'9b52427b0e3f827acb9a79f48ca69ca1': {'decisions': [{'type': 'edit',
    'edited_action': {'name': 'get_weather',
     'args': {'city': 'Boston, U.K.'}}},
   {'type': 'approve'}]}}

We can then resume by passing a command into the same streaming loop:

翻译:我们可以通过将命令传递到相同的流式循环中来恢复:

python 复制代码
interrupts = []
for stream_mode, data in agent.stream(
    # 恢复中断
    Command(resume=decisions),  
    config=config,
    stream_mode=["messages", "updates"],
):
    # Streaming loop is unchanged
    if stream_mode == "messages":
        token, metadata = data
        if isinstance(token, AIMessageChunk):
            _render_message_chunk(token)
    if stream_mode == "updates":
        for source, update in data.items():
            if source in ("model", "tools"):
                _render_completed_message(update["messages"][-1])
            if source == "__interrupt__":
                interrupts.extend(update)
                _render_interrupt(update[0])
复制代码
工具消息: [{'type': 'text', 'text': "It's always sunny in Boston, U.K.!"}]
工具消息: [{'type': 'text', 'text': "It's always sunny in San Francisco!"}]


|The| weather| in| Boston|,| U|.K|.| is| always| sunny|!| And| it's| also| always| sunny| in| San| Francisco|!|

Streaming from sub-agents 从子代理流媒体

When there are multiple LLMs at any point in an agent, it's often necessary to disambiguate the source of messages as they are generated.

翻译:当在智能体的任何时刻有多个人工智能语言模型(LLM)时,通常需要在消息生成时对消息的来源进行消歧。

To do this, pass a name to each agent when creating it. This name is then available in metadata via the lc_agent_name key when streaming in "messages" mode.

翻译:要实现这一点,在创建每个代理时传递一个名称。当以"messages"模式流式传输时,此名称可在元数据中通过lc_agent_name键访问。

Below, we update the streaming tool calls example:

翻译:下面,我们更新流式工具调用示例:

  1. 我们用一个call_weather_agent工具替换我们的工具,该工具在内部调用一个代理
  2. 我们添加一个名称到每个代理
  3. 我们在创建流时指定subgraphs=True
  4. 我们的流处理与之前相同,但我们添加逻辑来跟踪使用create_agent的name参数激活的代理
python 复制代码
weather_agent = create_agent(
    model,
    tools=[get_weather],
    name="weather_agent",  
)
# 我们用一个call_weather_agent工具替换我们的工具,该工具在内部调用一个代理
def call_weather_agent(query: str) -> str:
    """Query the weather agent."""
    result = weather_agent.invoke({
        "messages": [{"role": "user", "content": query}]
    })
    return result["messages"][-1].text

# 我们创建一个新的智能体,该智能体使用call_weather_agent工具
agent = create_agent(
    model,
    tools=[call_weather_agent],
    name="supervisor",  
)

# 接下来,我们在流循环中添加逻辑来报告哪个代理正在发出令牌:
def _render_message_chunk(token: AIMessageChunk) -> None:
    if token.text:
        print(token.text, end="|")
    if token.tool_call_chunks:
        print(token.tool_call_chunks)


def _render_completed_message(message: AnyMessage) -> None:
    if isinstance(message, AIMessage) and message.tool_calls:
        print(f"Tool calls: {message.tool_calls}")
    if isinstance(message, ToolMessage):
        print(f"Tool response: {message.content_blocks}")


input_message = {"role": "user", "content": "What is the weather in Boston?"}
# 我们运行流循环,观察令牌是如何从supervisor代理流向weather_agent代理的:
current_agent = None
for  _, stream_mode, data in agent.stream(
    {"messages": [input_message]},
    stream_mode=["messages", "updates"],
    # 启用子图模式
    subgraphs=True,  
    config={"configurable": {"model":"MiniMaxAI/MiniMax-M2"}}
):
    if stream_mode == "messages":
        token, metadata  = data
        # lc_agent_name 是一个元数据字段,用于标识当前正在处理的智能体
        if  metadata .get("lc_agent_name"):  
            agent_name = metadata .get("lc_agent_name")
            print("【【【【",agent_name)
            if agent_name != current_agent:  
                print(f" {agent_name}: ")  
                current_agent = agent_name  
        if isinstance(token, AIMessage):
            _render_message_chunk(token)
    if stream_mode == "updates":
        for source, update in data.items():
            if source in ("model", "tools"):
                _render_completed_message(update["messages"][-1])
复制代码
|[{'name': 'call_weather_agent', 'args': '', 'id': '019beb786ab90cafb8e5b13d98823b08', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"query": "weather in Boston"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Tool calls: [{'name': 'call_weather_agent', 'args': {'query': 'weather in Boston'}, 'id': '019beb786ab90cafb8e5b13d98823b08', 'type': 'tool_call'}]



|[{'name': 'get_weather', 'args': '', 'id': '019beb7874ec32095fc9b6cf3eb1b237', 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '{', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '"city": "Boston"', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
[{'name': None, 'args': '}', 'id': None, 'index': 0, 'type': 'tool_call_chunk'}]
Tool calls: [{'name': 'get_weather', 'args': {'city': 'Boston'}, 'id': '019beb7874ec32095fc9b6cf3eb1b237', 'type': 'tool_call'}]
Tool response: [{'type': 'text', 'text': "It's always sunny in Boston!"}]


|The| weather| in| Boston| is| currently| sunny|.| If| you| need| more| details| or| have| any| other| questions|,| feel| free| to| ask|!|Tool response: [{'type': 'text', 'text': '\n\nThe weather in Boston is currently sunny. If you need more details or have any other questions, feel free to ask!'}]


|The| weather| in| Boston| is| currently| sunny|.| If| you| need| more| details| or| have| any| other| questions|,| feel| free| to| ask|!|

禁用流媒体

在某些应用中,您可能需要禁用特定模型的单个标记流。这在以下情况下很有用:

  • 与多智能体系统合作以控制哪些智能体流式传输其输出
  • 混合支持流传输的模型与不支持的模型
  • 部署到LangSmith,并希望阻止某些模型输出被流式传输到客户端

设置streaming=False在初始化模型时。

python 复制代码
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="gpt-4o",
    streaming=False
)

在部署到LangSmith时,设置streaming=False 将不会流式传输任何您不希望到客户端的模型的输出。这在部署前的图形代码中进行配置。

并非所有的聊天模型集成都支持streaming参数。如果您的模型不支持它,请使用disable_streaming=True代替。此参数通过基类在所有聊天模型中都可用。

相关

  • 前端流媒体 --- 使用 useStream 构建实时代理交互的 React UI
  • 使用聊天模型进行流式传输 --- 直接从聊天模型流式传输令牌,无需使用代理或图形
  • 带有人类参与的流处理 --- 在处理人类审查的中断时,显示流代理进度
  • LangGraph 流式处理 --- 高级流式处理选项包括 values, debug 模式,和子图流式处理