Chainlit vs Streamlit和Gradio:为什么Chainlit是开发聊天机器人不错的选择

如果你对这篇文章感兴趣,而且你想要了解更多关于AI领域的实战技巧,可以关注「技术狂潮AI」公众号。在这里,你可以看到最新最热的AIGC领域的干货文章和案例实战教程。

一、前言

随着大语言模型(LLM)的快速发展,国内外越来越多的大模型蜂拥而至,真可谓是百花齐放,相当热闹的掀起了一次百模大战,甚至是千模大战。然而,面对这么多的大模型,个个都声称占据排行榜第一,到底实际效果如何,最终还得需要花大量时间去进行验证,有没有比较简单快速的方式来实现模型的快速体验呢。

目前关于这方面比较成熟的方案包括 Streamlit、Gradio 等Web UI框架,基本上想要实现 ChatGPT/Claude 类似的聊天机器人或者文档机器人,只需要很少量的代码就可以实现。 这些 UI 框架可以帮助机器学习工程师和数据科学家将他们的模型能力快速转换为交互式 Web 应用程序,使得他们可以更加方便地展示和验证模型效果。这种思路在 Gradio 和 Streamlit 中得到了很好的体现。这些框架提供了简单易用的 API 和丰富的可视化组件,使得用户可以用少量代码快速构建交互式应用程序。因此,这些 UI 框架不仅可以提高开发效率,还可以帮助机器学习工程师和数据科学家更好地展示他们的工作成果。

从“私人”聊天机器人到多模态和语音聊天机器人,以及用于文档学习的聊天机器人,我探索了各种各样的人工智能语言应用。这些项目基本上都是使用 Streamlit 或者 Gradio 开发的,它们是一个非常强大但轻量级的库,即使你作为Python开发工程师,不具备任何 Web 框架或编码经验,也可以快速创建 Web 应用程序。

以上是一个用 Streamlit 构建的 Streamlit docs 文档聊天机器人。

二、Chainlit 概述

Chainlit 是一个开源 Python 包,可以以惊人的速度构建和共享 LLM 应用程序,彻底改变了我们构建和共享语言模型应用程序的方式。将 Chainlit API 集成到您现有的代码中,在几分钟内生成类似 ChatGPT 的界面!

Chainlit 与 Streamlit 的不同之处在于它能够可视化模型的中间步骤和思维过程,让您深入了解它如何获得特定的生成输出。这使其成为理解和调试语言模型决策处理的强大工具。 在本文中,我们将展示如何在本地安装 Chainlit,重点介绍其主要功能,并探索其与不同应用程序的无缝集成。了解 Chainlit 在 UI 和交互方面的优势。

2.1、主要特征

  1. 快速构建 LLM 应用程序:与现有代码库无缝集成或在几分钟内从头开始

  2. 可视化多步骤推理:一目了然地了解产生输出的中间步骤

  3. 迭代提示:深入了解 Prompt Playground 中的提示,了解哪里出了问题并进行迭代

  4. 与团队协作:邀请您的队友,创建带注释的数据集并一起运行实验

  5. 分享您的应用程序:发布您的 LLM 应用程序并与世界分享(即将推出)

2.2、集成能力

Chainlit 与所有 Python 程序和库兼容。话虽这么说,它附带了一组与流行的库和框架的集成。

LangChain:https://docs.chainlit.io/integrations/langchain

Llama Index:https://docs.chainlit.io/integrations/llama-index

Haystack:https://docs.chainlit.io/integrations/haystack

Langflow:https://docs.chainlit.io/integrations/langflow

三、Chainlit 安装

Chainlit 需要 python>=3.8 ,你可以通过 pip 安装 Chainlit,如下所示:

pip install chainlit

安装完成后,可以使用 chainlit 命令在您的系统上可用。

Usage: chainlit [OPTIONS] COMMAND [ARGS]...

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  create-secret
  hello
  init
  run

当我们执行 hello 命令,系统会默认打开浏览器展示一个 chainlit UI 聊天机器人并询问你的名字,如下所示:

chainlit hello

四、构建基础的聊天机器人

接下来我们开始使用 Chainlit 基于 ChatGPT API 来创建一个最基本的 AI 聊天机器人。代码甚至比 Streamlit 实现还要少。 首先,我们安装 Chainlit 和 OpenAI 依赖项。

pip install chainlit and openai

在代码中,导入两个模块:

import openai
import chainlit as cl

提供我们的 OpenAI API 密钥:

openai.api_key = "{Your_API_Key}"

不同处理阶段的事件处理程序在 Chainlit 中被定义为装饰器。这里我们定义 on_chat_start 回调来初始化未来提示的系统角色消息,以在新图表启动时在Chainlit的 user_session 对象中列出 message_history 。与Streamlit 中的 session_state 功能一样, user_session 可用于存储Web会话之间的缓存数据。

@cl.on_chat_start
def start_chat():
    cl.user_session.set(
        "message_history",
        [{"role": "system", "content": "You are a helpful assistant."}],
    )

如果您熟悉OpenAI ChatCompletion API,您应该知道 gpt-3.5-turbo 模型的聊天完成提示定义如下结构:

[
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": message},
    {"role": "assistant", "content": message},
    {"role": "user", "content": message},
    {"role": "assistant", "content": message},
    ...
    "role": "user", "content": message}
]

提示正文中的用户助理对话对为语言模型提供了历史上下文以供其参考。对于用户输入的每条新消息,都需要定义一个装饰器 on_message 。 首先,将新的用户输入推送到 message_history 中。

def main(message: str):
    message_history = cl.user_session.get("message_history")
    message_history.append({"role": "user", "content": message})

然后使用消息正文调用 openai.ChatCompletion.create() 方法以远程生成 GPT-3.5 模型的响应。这与其他聊天机器人开发的过程完全相同。

completion = openai.ChatCompletion.create(
        model=model_name, messages=message_history
    )

通过Chainlit的 Message() 方法创建msg对象,并填充响应内容。

msg = cl.Message(content="")
msg.stream_token(completion.choices[0].message.content)

将响应添加到 message_history 作为辅助数据。

message_history.append({"role": "assistant", "content": msg.content})

将响应发送到用户的屏幕。

msg.send()

整个代码实现很简单如下:

import openai
import chainlit as cl

openai.api_key = "{Your_API_Key}"

model_name = "gpt-3.5-turbo-0613"


@cl.on_chat_start
def start_chat():
    cl.user_session.set(
        "message_history",
        [{"role": "system", "content": "You are a helpful assistant."}],
    )


@cl.on_message
async def main(message: str):
    message_history = cl.user_session.get("message_history")
    message_history.append({"role": "user", "content": message})

    msg = cl.Message(content="")

    completion = openai.ChatCompletion.create(
        model=model_name, messages=message_history
    )

    await msg.stream_token(completion.choices[0].message.content)

    message_history.append({"role": "assistant", "content": msg.content})
    await msg.send()

要在您的计算机上运行该应用程序,您应该在终端中输入的唯一命令如下(假设代码文件是“app.py”):

chainlit run app.py 

如果一切顺利,你的浏览器将自动显示一个显示您的聊天机器人的新选项卡,你也可以通过默认地址 localhost:8000 手动打开它。

五、构建SQL脚本生成器

让我们继续来构建一个简单的应用程序,帮助用户使用自然语言创建 SQL 查询。

5.1、安装依赖

pip install chainlit openai

5.2、导入模块

import chainlit as cl
import openai
import os

openai.api_key = "YOUR_OPEN_AI_API_KEY"

5.3、定义提示和 LLM 设置

template = """SQL tables (and columns):
* Customers(customer_id, signup_date)
* Streaming(customer_id, video_id, watch_date, watch_minutes)

A well-written SQL query that {input}:
```"""


settings = {"model": "gpt-3.5-turbo","temperature": 0,"max_tokens": 500,"top_p": 1,"frequency_penalty": 0,"presence_penalty": 0,"stop": ["```"],}

5.4、监听并回复

我们使用 @on_message 装饰器来装饰 main 函数,以告诉 Chainlit 在用户每次发送消息时运行 main 函数。然后,我们使用 Message 类将答案发送回 UI。

@cl.on_message
async def main(message: str):
    # 为提示游乐场创建提示对象
    prompt = Prompt(
        provider=ChatOpenAI.id,
        messages=[
            PromptMessage(
                role="user",
                template=template,
                formatted=template.format(input=message)
            )
        ],
        settings=settings,
        inputs={"input": message},
    )

    # 准备消息进行流式传输
    msg = cl.Message(
        content="",
        language="sql",
    )

    # 调用OpenAI
    async for stream_resp in await openai.ChatCompletion.acreate(
        messages=[m.to_openai() for m in prompt.messages], stream=True, **settings
    ):
        token = stream_resp.choices[0]["delta"].get("content", "")
        await msg.stream_token(token)

    # 随着完成情况更新提示对象
    prompt.completion = msg.content
    msg.prompt = prompt

    # 发送并关闭消息流
    await msg.send()

5.5、运行

chainlit run app.py -w

六、构建高级文档聊天机器人

前面我面基于 OpenAI API 完成了一个基础的 GPT 聊天机器人和SQL脚本生成器,接下来我们将创建一个高级的聊天机器人,以支持文档问答。它的用法与使用 Streamlit 小部件创建的聊天机器人非常相似,用于上传 pdf 文件并查询该文件,以提高文档学习的效率。 与 Streamlit 相比,通过使用带有嵌入式 LangChain 组件的 Chainlit,可以使用更少的代码行,但实现更丰富的聊天机器人功能,包括 2 个主要功能: a) 显示 LangChain 链中思维过程的中间步骤

b) 标有可点击 ID 的“源”内容反映了原始文档中引用的上下文。

6.1、开发

LangChain 已集成到 Chainlit 库中,我们不再需要独立管理语言模型的生成过程。由于该应用的目标是通过现有的链 RetrievalQAWithSourcesChain 实现的。

1)、将代码下载到本地。

git clone https://github.com/Crossme0809/chainlit-langchain-chatbot.git
cd chainlit-langchain-chatbot

2)、将 example.env 重命名为 .env,并使用 cp example.env .env 并输入 OpenAI API 密钥,如下所示。从此 URL 获取 OpenAI API 密钥。如果您还没有的话,您需要在 OpenAI 网站中创建一个帐户。

OPENAI_API_KEY=your_openai_api_key

3)、创建一个 virtualenv 并激活它

python3 -m venv .venv && source .venv/bin/activate

4)、如果你有 python 3.11,那么上面的命令就可以了。但是,如果你的 python 版本低于 3.11。使用 conda 更容易。首先确保你已经安装了 conda。然后运行以下命令。

conda create -n .venv python=3.11 -y && source activate .venv

5)、在终端中运行以下命令来安装必要的 python 包:

pip install -r requirements.txt

chromadb 用于存储和检索 OpenAI 嵌入函数生成的矢量化数据。 pypdf 用于将 pdf 文件转换为将被矢量化的文本文件。 6)、导入所有必要的模块并定义环境变量

# 导入必要模块,定义env变量

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQAWithSourcesChain
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
import os
import io
import chainlit as cl
import PyPDF2
from io import BytesIO

from dotenv import load_dotenv

# 从. env文件加载环境变量
load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

7)、上传并转换pdf文件

files = None

# 等待用户上传PDF文件
while files is None:
    files = await cl.AskFileMessage(
        content="请上传PDF文档!",
        accept=["application/pdf"],
        max_size_mb=20,
        timeout=180,
    ).send()

file = files[0]

msg = cl.Message(content=f"Processing `{file.name}`...")
await msg.send()

# Read the PDF file
pdf_stream = BytesIO(file.content)
pdf = PyPDF2.PdfReader(pdf_stream)
pdf_text = ""
for page in pdf.pages:
    pdf_text += page.extract_text()

AskFileMessage 小部件用于接收用户上传并将文件保存到 ./doc/ 文件夹。尽管 LangChain 中的 document_loader 支持包括pdf在内的各种文件类型,但很难将自定义元数据从文本文件以外的文件附加到矢量存储。定制的元数据将用于源检索。

8)、设置链条

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)

# 将文本分成块
texts = text_splitter.split_text(pdf_text)

# 为每个块创建元数据
metadatas = [{"source": f"{i}-pl"} for i in range(len(texts))]

# 创建 Chroma 向量数据库存储
embeddings = OpenAIEmbeddings()
docsearch = await cl.make_async(Chroma.from_texts)(
    texts, embeddings, metadatas=metadatas
)

# 创建一个使用Chroma向量存储的链
chain = RetrievalQAWithSourcesChain.from_chain_type(
    ChatOpenAI(temperature=0),
    chain_type="stuff",
    retriever=docsearch.as_retriever(),
)

# 在用户会话中保存元数据和文本
cl.user_session.set("metadatas", metadatas)
cl.user_session.set("texts", texts)

# 让用户知道系统已准备就绪
msg.content = f"Processing `{file.name}` done. You can now ask questions!"
await msg.update()

cl.user_session.set("chain", chain)

要创建链 RetrievalQAWithSourcesChain ,我们应该将文本文件分成几个块,并相应地附加元数据以识别每个相关块。然后,从块转换的矢量数据应存储在 Chroma 存储中。矢量化由 OpenAIEmbeddings() API 完成。在 langchain_factory 的最后一步中,我们使用定义 gpt-3.5-turbo 的语言模型以及矢量数据来创建链。

9)、用分块文本设置 user_session 供以后使用

cl.user_session.set("texts", texts)

10)、检索答案及其来源

langchain_post 装饰器中,处理函数将接收 LangChain 生成的响应。特别是在 RetrievalQAWithSourcesChain 中,响应将包括 AI 生成答案和您在 metadatas 中定义的来源标签。

@cl.on_message
async def main(message: str):
    chain = cl.user_session.get("chain")  # type: RetrievalQAWithSourcesChain
    cb = cl.AsyncLangchainCallbackHandler(
        stream_final_answer=True, answer_prefix_tokens=["FINAL", "ANSWER"]
    )
    cb.answer_reached = True
    res = await chain.acall(message, callbacks=[cb])

    answer = res["answer"]
    sources = res["sources"].strip()
    source_elements = []

    # 从用户会话中获取元数据和文本
    metadatas = cl.user_session.get("metadatas")
    all_sources = [m["source"] for m in metadatas]
    texts = cl.user_session.get("texts")

    if sources:
        found_sources = []

        # 将来源添加到消息中
        for source in sources.split(","):
            source_name = source.strip().replace(".", "")
            # 获取源的索引
            try:
                index = all_sources.index(source_name)
            except ValueError:
                continue
            text = texts[index]
            found_sources.append(source_name)
            # 创建消息中引用的文本元素
            source_elements.append(cl.Text(content=text, name=source_name))

        if found_sources:
            answer += f"\nSources: {', '.join(found_sources)}"
        else:
            answer += "\nNo sources found"

    if cb.has_streamed_final_answer:
        cb.final_stream.elements = source_elements
        await cb.final_stream.update()
    else:
        await cl.Message(content=answer, elements=source_elements).send()

在这里,我们从格式为“-pl”的源字符串中提取索引,并根据每个索引收集相应的文本块。原始文本块由 5) 发送。最后一步是使用 Chainlit Message 元素中提供的文本链接填写 Chainlit Message 的内容以及答案和来源,然后将其发送到 Web UI。

完整代码分享如下:

# 导入必要模块,定义env变量

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQAWithSourcesChain
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
import os
import io
import chainlit as cl
import PyPDF2
from io import BytesIO


from dotenv import load_dotenv

# 从. env文件加载环境变量
load_dotenv()

OPENAI_API_KEY= os.getenv("OPENAI_API_KEY")


# text_splitter和系统模板

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)

system_template = """Use the following pieces of context to answer the users question.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
ALWAYS return a "SOURCES" part in your answer.
The "SOURCES" part should be a reference to the source of the document from which you got your answer.

Example of your response should be:

```
The answer is foo
SOURCES: xyz
```

Begin!
----------------
{summaries}"""


messages = [
    SystemMessagePromptTemplate.from_template(system_template),
    HumanMessagePromptTemplate.from_template("{question}"),
]
prompt = ChatPromptTemplate.from_messages(messages)
chain_type_kwargs = {"prompt": prompt}


@cl.on_chat_start
async def on_chat_start():

    # 发送带有本地文件路径的图像
    elements = [
    cl.Image(name="image1", display="inline", path="./robot.jpeg")
    ]
    await cl.Message(content="你好,欢迎使用文档聊天机器人 AskDocumentQuery!", elements=elements).send()
    files = None

    # 等待用户上传PDF文件
    while files is None:
        files = await cl.AskFileMessage(
            content="请上传PDF文档!",
            accept=["application/pdf"],
            max_size_mb=20,
            timeout=180,
        ).send()

    file = files[0]

    msg = cl.Message(content=f"Processing `{file.name}`...")
    await msg.send()

    # Read the PDF file
    pdf_stream = BytesIO(file.content)
    pdf = PyPDF2.PdfReader(pdf_stream)
    pdf_text = ""
    for page in pdf.pages:
        pdf_text += page.extract_text()

    # 将文本分成块
    texts = text_splitter.split_text(pdf_text)

    # 为每个块创建元数据
    metadatas = [{"source": f"{i}-pl"} for i in range(len(texts))]

    # 创建 Chroma 向量数据库存储
    embeddings = OpenAIEmbeddings()
    docsearch = await cl.make_async(Chroma.from_texts)(
        texts, embeddings, metadatas=metadatas
    )

    # 创建一个使用Chroma向量存储的链
    chain = RetrievalQAWithSourcesChain.from_chain_type(
        ChatOpenAI(temperature=0),
        chain_type="stuff",
        retriever=docsearch.as_retriever(),
    )
    

    # 在用户会话中保存元数据和文本
    cl.user_session.set("metadatas", metadatas)
    cl.user_session.set("texts", texts)

    # 让用户知道系统已准备就绪
    msg.content = f"Processing `{file.name}` done. You can now ask questions!"
    await msg.update()

    cl.user_session.set("chain", chain)


@cl.on_message
async def main(message:str):

    chain = cl.user_session.get("chain")  # type: RetrievalQAWithSourcesChain
    cb = cl.AsyncLangchainCallbackHandler(
        stream_final_answer=True, answer_prefix_tokens=["FINAL", "ANSWER"]
    )
    cb.answer_reached = True
    res = await chain.acall(message, callbacks=[cb])

    answer = res["answer"]
    sources = res["sources"].strip()
    source_elements = []
    
    # 从用户会话中获取元数据和文本
    metadatas = cl.user_session.get("metadatas")
    all_sources = [m["source"] for m in metadatas]
    texts = cl.user_session.get("texts")

    if sources:
        found_sources = []

        # 将来源添加到消息中
        for source in sources.split(","):
            source_name = source.strip().replace(".", "")
            # 获取源的索引
            try:
                index = all_sources.index(source_name)
            except ValueError:
                continue
            text = texts[index]
            found_sources.append(source_name)
            # 创建消息中引用的文本元素
            source_elements.append(cl.Text(content=text, name=source_name))

        if found_sources:
            answer += f"\nSources: {', '.join(found_sources)}"
        else:
            answer += "\nNo sources found"

    if cb.has_streamed_final_answer:
        cb.final_stream.elements = source_elements
        await cb.final_stream.update()
    else:
        await cl.Message(content=answer, elements=source_elements).send()

在终端中运行以下命令来启动聊天 UI:

chainlit run pdf_qa.py -w
chainlit run txt_qa.py -w
chainlit run pdf_txt_qa.py -w
chainlit run csv_qa.py -w

您的浏览器将自动显示一个显示您的聊天机器人的新选项卡,您也可以通过默认地址 localhost:8000 手动打开它。

6.2、部署

与 Streamlit 不同,目前 Chainlit 无法自由部署在您的服务器上并远程发布给用户。到目前为止,通过 chainlit run 运行只会在端口 8000 上创建本地 Web 服务。好消息是,APP 部署已在他们即将推出的列表中,因此很快就能看到这一基本功能。

七、总结

本文主要介绍了大语言模型(LLM)的快速发展以及相关的应用框架,如Streamlit和Gradio。这些框架提供了简单易用的API和丰富的可视化组件,使开发人员能够快速构建交互式应用程序,并展示和验证模型效果。通过这些框架,机器学习工程师和数据科学家可以更方便地将模型能力转化为交互式Web应用,提高开发效率并展示他们的工作成果。

另外探索了各种人工智能语言应用,从"私人"聊天机器人到多模态和语音聊天机器人,以及用于文档学习的聊天机器人。这些项目都是使用Streamlit或Gradio开发的,这两个框架都是强大而轻量级的库,即使对于没有Web框架或编码经验的Python开发工程师来说,也可以快速创建Web应用程序。

总之,通过 Chainlit,开发人员可以利用类似 GPT 的聊天小部件和 LangChain 方法的强大功能,为用户和模型开发人员提供更自然的 AI 聊天机器人体验。

八、References

  • Chainlit

  • https://github.com/Chainlit/chainlit

  • Chainlit docs

  • https://docs.chainlit.io/overview

如果你对这篇文章感兴趣,而且你想要了解更多关于AI领域的实战技巧,可以关注「技术狂潮AI」公众号。在这里,你可以看到最新最热的AIGC领域的干货文章和案例实战教程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术狂潮AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值