9. LangChain流式输出

一篇涵盖agent和工具的简单流到复杂流的指南。

对于LLM来说,流式传输已成为一个越来越受欢迎的特性。他的理念是在生成过程中迅速返回令牌,而不是等到完整响应生成后再返回内容。

对于简单的用例来说,流实际上非常容易实现,但当我们开始引入像agent这样具有自身运行逻辑的东西时,它可能变得复杂,因为这些逻辑可能会阻碍我们进行流的尝试。幸运的是,我们可以让他起作用----只是需要一些额外的努力。

我们将从简单的实现开始,为大语言模型在终端中实现流式传输,但是在笔记的最后,我们将通过FastApi为Agent实现更加复杂的流任务。

首先,让我们安装所有我们即将使用的library。

!pip install -qU \
    openai==0.28.0 \
    langchain==0.0.301 \
    fastapi==0.103.1 \
    "uvicorn[standard]"==0.23.2

LLM流式传输到stdout

流的最简单的形式就是是简单的在生成token时将他们打印出来。为了设置这个,我们需要使用两个特定的参数初始化一个支持流式传输的LLM(并不是所有的模型都支持)。

  • streaming=True,打开流式传输
  • callbacks=[SomeCallBackHere()],在这里传递一个LangChain的callback类,(或者多个callback的列表)。

streaming参数不言而喻。回调参数和回调类则不太好理解---他们实际上充当一些小代码块,在我们的LLM生成每个令牌是执行某些操作。如前所述,流式传输最简单的形式是,在生成token时打印他们,例如使用StreamingStdOutCallbackHandler

import os
from langchain.chat_models import ChatOpenAI
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") or "YOUR_API_KEY"

llm = ChatOpenAI(
    openai_api_key=os.getenv("OPENAI_API_KEY"),
    temperature=0.0,
    model_name="gpt-3.5-turbo",
    streaming=True,  # ! important
    callbacks=[StreamingStdOutCallbackHandler()]  # ! important
)

如果我们运行LLM,我们将看到响应正在被流式输出的。

from langchain.schema import HumanMessage

# create messages to be passed to chat LLM
messages = [HumanMessage(content="tell me a long story")]

llm(messages)

这出奇的容易,但一旦我们开始使用aagent,事情就会变得复杂的多。让我们先初始化一个agent。

from langchain.memory import ConversationBufferWindowMemory
from langchain.agents import load_tools, AgentType, initialize_agent

# initialize conversational memory
memory = ConversationBufferWindowMemory(
    memory_key="chat_history",
    k=5,
    return_messages=True,
    output_key="output"
)

# create a single tool to see how it impacts streaming
tools = load_tools(["llm-math"], llm=llm)

# initialize the agent
agent = initialize_agent(
    agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION,
    tools=tools,
    llm=llm,
    memory=memory,
    verbose=True,
    max_iterations=3,
    early_stopping_method="generate",
    return_intermediate_steps=False
)

我们已经将StreamingStdOutCallbackHandler添加到代理中,因为我们使用了与该回调相同的LLM。那么让我们看看在运行代理时会得到什么。

prompt = "Hello, how are you?"

agent(prompt)

不错,但我们现在面临的问题是如何流式传输来自LLM的整个输出。因为我们正在使用agent,所以LLM被指示输出我们这里看到的JSON格式,以便agent逻辑可以处理工具使用,多'思考'步骤,等等。例如,如果我们问一个数学问题,我们将看到以下内容:

agent("what is the square root of 71?")

在开发过程中看到这个很有趣,但在实际用例中,我们都希望对这个流进行一些清理。为此,我们可以采用两种方法---要门构建自定义回调处理器,要么使用LangChain中专门构建的回调处理器(像往常一样,LangChain对任何情况都有解决方案)。让我们来尝试LangChain专门构建的FinalStreamingStdOutCallbackHandler

我们将覆盖在这里找到的已有的callbacks属性:

agent.agent.llm_chain.llm

使用新的回调器:

from langchain.callbacks.streaming_stdout_final_only import (
    FinalStreamingStdOutCallbackHandler,
)

agent.agent.llm_chain.llm.callbacks = [
    FinalStreamingStdOutCallbackHandler(
        answer_prefix_tokens=["Final", "Answer"]
    )
]

让我们来试用一下:

agent("what is the square root of 71?")

不太对,我们确实应该清理一下answer_prefix_tokens参数,但这很难做大。通常使用自定义回调处理器会更容易,如下所示:

import sys

class CallbackHandler(StreamingStdOutCallbackHandler):
    def __init__(self):
        self.content: str = ""
        self.final_answer: bool = False

    def on_llm_new_token(self, token: str, **kwargs: any) -> None:
        self.content += token
        if "Final Answer" in self.content:
            # now we're in the final answer section, but don't print yet
            self.final_answer = True
            self.content = ""
        if self.final_answer:
            if '"action_input": "' in self.content:
                if token not in ["}"]:
                    sys.stdout.write(token)  # equal to `print(token, end="")`
                    sys.stdout.flush()

agent.agent.llm_chain.llm.callbacks = [CallbackHandler()]

让我们再试一次:

agent("what is the square root of 71?")

agent.agent.llm_chain.llm

这个不算完美,但情况正在好转。现在,在绝大多数场景,我们不太可能只是简单的打印输出到终端或者一个notebook。当我们想要做一些更复杂的事情,比如将数据流式输入到另外一个API,我们需要做一些不一样的事情。

在Agent中使用FastAPI

在大多数场景中,我们需要将我们的LLM、Agent等等设置到API之后。让我们把这个也加入进来,看看如何使用FastApi为Agent实现流式传输。

首先,我们将创建一个简答的main.py脚本来包含我们的FastAPI逻辑。源码看这里

为了执行这个API,请导航到该目录并运行uvicorn main:app --reload。一旦完成,你可以通过查看下一个单元的输出中的🤙状态来确认他正在运行:

import requests

res = requests.get("http://localhost:8000/health")
res.json()

{'status': '🤙'}

res = requests.get("http://localhost:8000/chat",
    json={"text": "hello there!"}
)
res

<Response [200]>

res.json()

{'input': 'hello there!',
 'chat_history': [],
 'output': 'Hello! How can I assist you today?'}

与我们的Stdout流式输出不同,我们现在需要将我们的token传递给一个生成器函数,该函数通过StreamingResponse对象将这些令牌传递给FastAPI。为此,我们需要使用异步代码,否则我们的生成器在生成完成之前并不会开始发出任何事情。

我们的回调处理器访问队列,每当token生成时,它将token放入到队列中。我们的生成函数异步的检测队列中是否有新的token被添加进来。一旦生成器看到有令牌被添加,它就会获取该令牌并将其传递给我们的StreamingResponse

为了看到他的实际效果,我们将定义一个名为get_stream的流请求函数:

def get_stream(query: str):
    s = requests.Session()
    with s.get(
        "http://localhost:8000/chat",
        stream=True,
        json={"text": query}
    ) as r:
        for line in r.iter_content():
            print(line.decode("utf-8"), end="")

get_stream("hi there!")

"Hello! How can I assist you today?"
相关推荐
2401_88304108几秒前
新锐品牌电商代运营公司都有哪些?
大数据·人工智能
魔道不误砍柴功5 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
_.Switch30 分钟前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
AI极客菌1 小时前
Controlnet作者新作IC-light V2:基于FLUX训练,支持处理风格化图像,细节远高于SD1.5。
人工智能·计算机视觉·ai作画·stable diffusion·aigc·flux·人工智能作画
阿_旭1 小时前
一文读懂| 自注意力与交叉注意力机制在计算机视觉中作用与基本原理
人工智能·深度学习·计算机视觉·cross-attention·self-attention
王哈哈^_^1 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
Power20246662 小时前
NLP论文速读|LongReward:基于AI反馈来提升长上下文大语言模型
人工智能·深度学习·机器学习·自然语言处理·nlp
数据猎手小k2 小时前
AIDOVECL数据集:包含超过15000张AI生成的车辆图像数据集,目的解决旨在解决眼水平分类和定位问题。
人工智能·分类·数据挖掘
好奇龙猫2 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法