使用Chainlit接入通义千问快速实现一个本地文档知识问答机器人增强版

news2024/9/23 9:30:15

前言

之前写了一篇文章,使用国内通义千问作为llm,结合langchain框架实现文本向量化检索和使用chainlit实现网页界面交互,实现一个本地知识问答的机器人。原文链接《使用Chainlit接入通义千问快速实现一个本地文档知识问答机器人》。本次基于上个版本做了增强优化,重要改动是:

  • 处理txt文本以外支持pdf文档的知识问答
  • 使用流式响应提升用户体验

下面是文章教程

教程

文档问答机器人实现示例
在此示例中,我们将构建一个聊天机器人 QA 应用。我们将学习如何:

  • 上传文件
  • 从文件创建向量嵌入
  • 创建一个聊天机器人应用程序,能够显示用于生成答案的来源

先决条件

安装项目所需依赖。
在项目根目录下创建 requirements.txt 文件,配置需要的依赖内容如下:

chainlit~=1.1.306
openai~=1.37.0
langchain~=0.2.11
chromadb~=0.4.24
tiktoken~=0.7.0
dashscope~=1.20.3

使用命令公爵切换到项目执行以下命令安装:

pip install -r .\requirements.txt

然后,您需要去这里创建一个 OpenAI 密钥。没有可以使用国内的通义千问或者百度文心一言的。具体文章看之前的《使用Chainlit接入通义千问快速实现一个多模态的对话应用》。

使用 LangChain 进行对话式文档 QA

项目根目录下创建文件pdf_qa.py

import chainlit as cl
from chainlit.types import AskFileResponse
from langchain.callbacks.base import AsyncCallbackHandler
from langchain.chains import (
    ConversationalRetrievalChain,
)
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import PyPDFLoader, TextLoader
from langchain.embeddings.dashscope import DashScopeEmbeddings
from langchain.memory import ChatMessageHistory, ConversationBufferMemory
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma

text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50)
index_name = "langchain-demo"
# Create a Chroma vector store
embeddings = DashScopeEmbeddings()
author = "Tarzan"


def process_file(file: AskFileResponse):
    loader = None
    if file.type == "text/plain":
        loader = TextLoader(file.path, encoding="utf-8")
    elif file.type == "application/pdf":
        loader = PyPDFLoader(file.path)
    documents = loader.load()
    docs = text_splitter.split_documents(documents)
    for i, doc in enumerate(docs):
        doc.metadata["source"] = f"source_{i}"
    return docs


def get_docsearch(file: AskFileResponse):
    docs = process_file(file)

    # Save data in the user session
    cl.user_session.set("docs", docs)

    docsearch = Chroma.from_documents(
        docs, embeddings, collection_name=index_name
    )
    return docsearch


@cl.on_chat_start
async def on_chat_start():
    files = None

    # Wait for the user to upload a file
    while files is None:
        files = await cl.AskFileMessage(
            content="Please upload a text file to begin!",
            accept=["text/plain", "application/pdf"],
            max_size_mb=20,
            timeout=180,
        ).send()

    file = files[0]

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

    message_history = ChatMessageHistory()

    memory = ConversationBufferMemory(
        memory_key="chat_history",
        output_key="answer",
        chat_memory=message_history,
        return_messages=True,
    )
    # Create a chain that uses the Chroma vector store
    chain = ConversationalRetrievalChain.from_llm(
        ChatOpenAI(model_name="qwen-turbo", temperature=0, streaming=True),
        chain_type="stuff",
        retriever=docsearch.as_retriever(),
        memory=memory,
        return_source_documents=True,
    )

    # Let the user know that the system is ready
    msg.content = f"Processing `{file.name}` done. You can now ask questions!"
    await msg.update()

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


class AsyncLangchainCallbackHandler(AsyncCallbackHandler):
    def __init__(self, message: cl.Message):
        self.message = message

    async def on_llm_new_token(self, token: str, **kwargs) -> None:
        await self.message.stream_token(token)


@cl.on_message
async def main(message: cl.Message):
    msg = cl.Message(content="", elements=[], author=author)
    await msg.send()
    chain = cl.user_session.get("chain")
    # 创建回调处理器实例
    cb = AsyncLangchainCallbackHandler(msg)
    res = await chain.acall(message.content, callbacks=[cb])
    source_documents = res["source_documents"]
    text_elements = []
    if source_documents:
        for source_idx, source_doc in enumerate(source_documents):
            source_name = f"source_{source_idx}"
            # Create the text element referenced in the message
            text_elements.append(
                cl.Text(content=source_doc.page_content, name=source_name, display="side")
            )
        source_names = [text_el.name for text_el in text_elements]

        if source_names:
            await msg.stream_token(f"\nSources: {', '.join(source_names)}")
            msg.elements = text_elements
        else:
            await msg.stream_token("\nNo sources found")
    await msg.update()

代码解释

这段代码是一个使用 Chainlit 库构建的交互式文档问答应用。

导入必要的库和模块

首先,导入了必要的库和模块,例如 Chainlit 中用于处理用户交互的功能,LangChain 用于构建文档问答系统的组件,以及文件加载器、文本分割器等工具。

定义文本分割器和索引名称

text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50)
index_name = "langchain-demo"

这里定义了一个递归字符文本分割器,用于将长文档拆分成更小的块,以便于处理和索引。同时定义了向量数据库的集合名称。

定义作者变量

author = "Tarzan"

这个变量用于设置消息的发送者名称。

文件处理函数

def process_file(file: AskFileResponse):
    ...

此函数根据上传文件的类型(纯文本或 PDF)加载文档,并使用前面定义的文本分割器对文档进行分割。然后更新每个文档元数据中的源信息,并返回分割后的文档列表。

向量数据库创建函数

def get_docsearch(file: AskFileResponse):
    ...

该函数调用 process_file 函数来处理文件,并将处理后的文档存储到 Chroma 向量数据库中。此外,它还会将这些文档保存在用户的会话中。

对话开始时触发的函数

@cl.on_chat_start
async def on_chat_start():
    ...

当对话开始时,这个函数会被触发。它等待用户上传一个文件,然后调用 get_docsearch 函数来创建向量数据库,并初始化一个对话检索链,准备回答用户的问题。

异步回调处理器类

class AsyncLangchainCallbackHandler(AsyncCallbackHandler):
    ...

这是一个自定义的回调处理器类,用于处理来自 LangChain 的流式输出。每当模型生成一个新的令牌,它就会调用 on_llm_new_token 方法,将生成的文本流式发送给用户。

消息处理函数

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

当收到用户的消息时,这个函数会被调用。它从用户的会话中获取之前初始化的对话检索链,然后调用这个链来回答问题。同时,它也会将相关的源文档以文本元素的形式展示给用户。

总的来说,这段代码实现了一个简单的问答系统,能够处理用户上传的文档,并针对这些文档回答用户提出的问题。它利用了 LangChainChainlit 的功能,使得整个交互过程流畅且易于使用。

环境变量

项目根目录下,创建.env文件,配置如下:

OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
OPENAI_API_KEY="通义千文API-KEY"
DASHSCOPE_API_KEY="通义千文API-KEY"

启动命令

  • 命令行工具,项目根目录下执行
chainlit run pdf_qa.py

启动UI示例

  • txt文件
    在这里插入图片描述
  • pdf文件
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1976655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

七夕情人节有什么好物推荐?五款性价比超高的产品推荐!

亲爱的朋友们,随着七夕情人节的临近,空气中弥漫着浪漫与甜蜜的气息。在这个专属恋人的节日里,团团知道大家在为心爱的人挑选礼物时可能会感到纠结。因此,我根据个人的浪漫经验和精心的市场挑选,为大家准备了一份情人节…

商家接单业务

文章目录 概要整体架构流程技术细节小结 概要 商家接单是电子商务、外卖平台、在线零售等多个行业中的一项核心业务流程。这项功能允许商家接收来自客户的订单,并对其进行处理。 需求分析以及接口设计 技术细节 1.Controller层: /*** 接单* param orderConfirmD…

常回家看看之tcachebin-attack

常回家看看之tcachebin-attack 自从glibc2.26之后出现了新的堆管理机制,及引用了tcachebin机制,tcachebin也是主要分配小堆块的,有40条bin链(0x10 - 0x410) 那么这样的分配有很多和smallbin 和fastbin重叠的部分&…

使用labelme生成mask数据集(亲测可行)

1、下载label.exe文件 链接:github地址 2、安装一下anaconda,百度一下直接安装就行 3、打开labelme.exe文件,直接加载图片,然后编辑多边形,就是mask的位置 4、画好mask了,保存为json文件,记住这…

【课程总结】Day17(中):LSTM及GRU模型简介

前言 在上一章【课程总结】Day17(上):NLP自然语言处理及RNN网络我们初步了解RNN的基本概念和原理。本章内容,我们将继续了解RNN的变种模型,如LSTM和GRU。 RNN发展历史 早期发展 1980年代:RNN 的概念最早由 David Rumelhart 和…

盘点一下这几个月以来的大事记吧~图欧学习资源库更新日志(2022年5月~10月)含资源

大家好,我是TUO图欧君!好久不见~ 这几个月以来我都干了什么呢?到底是因为什么事情拖更呢?咳咳……说来话长……总的来说,更加完善了图欧学习资源库网站,并且升级了三大网盘的内容空间,资源更加…

亚马逊与Temu联动:揭秘差价新玩法

摘要: 最近,跨境电商里有一种新颖的玩法悄然兴起——在亚马逊开店,通过在Temu下单并直接发货给亚马逊客户,从而赚取差价。 这种模式不仅降低了库存压力,还能实现利润最大化。 甚至有些铁子,能在这个制度下…

基于Java+SpringBoot+Vue的母婴商城

基于JavaSpringBootVue的母婴商城 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 哈喽兄弟们…

day16 Java基础——JavaDoc生成文档

day16 Java基础——JavaDoc生成文档 目录 day16 Java基础——JavaDoc生成文档1. 什么是JavaDoc2. 生成JavaDoc2.1 通过命令行生成JavaDoc2.2 使用IDEA生成JavaDoc 1. 什么是JavaDoc JavaDoc是一种标准的、用于生成Java代码API文档的工具。它通过在Java源代码中特定的注释标签&…

【letcode-c++】242有效的字母异位词与49字母异位词分组

一、242 有效的字母异位词 (1)题目 (2)知识点–哈希 【这一段总结来自于代码随想录的讲解学透哈希表 哈希的优势是可以实现快速查找,它非常适合应用与查找某一个元素是否在一个集合中出现。 哈希有三种实现形式&…

arduino程序-模拟输入(基础知识)

arduino程序-模拟输入(基础知识) 1-28 模拟输入1 - 学用电位器电位器电位器实际应用Arduino如何接电位器 1-29 模拟输入2-analogRead演示效果示例程序:干扰问题AnalogRead() 1-30 模拟输入3-电位器控制LED亮度实验演示…

自定义封装日历组件

自定义日历 工作需要&#xff0c;但现有框架封装的日历无法满足需求&#xff0c;又找不到更好的插件&#xff0c;所以准备自己封装一个。 效果图和说明 一个很简易版的demo日历&#xff0c;本文只提供最基本的功能代码&#xff0c;便于阅读二开。 新建calendar.vue文件 <…

【小技巧】CSS如何实现文字溢出显示省略号

文章目录 文字溢出显示省略号设置伪类实现全称展示 文字溢出显示省略号 在列表项或者导航菜单中&#xff0c;经常会在列表项或导航菜单中&#xff0c;由于空间有限&#xff0c;当文本内容较长时&#xff0c;可以使用省略号显示文本已被截断。 CSS中文字溢出显示省略号&#xf…

TCP通信的实现和项目案例

TCP协议是面向连接的&#xff0c;在通信时客户端与服务器端必须建立连接。在网络通讯中&#xff0c;第一次主动发起通讯的程序被称作客户端&#xff08;Client&#xff09;程序&#xff0c;简称客户端&#xff0c;而在第一次通讯中等待连接的程序被称作服务器端&#xff08;Ser…

npm install 报错 ‘proxy‘ config is set properly. See: ‘npm help config‘

解决 参考链接&#xff1a;npm install 报错 ‘proxy‘ config is set properly. See: ‘npm help config‘-阿里云开发者社区 (aliyun.com)

Linux Ubuntu 20.04 安装DPDK方法指南

系统及DPDK版本 系统&#xff1a;Ubuntu 20.04 DPDK&#xff1a;20.11.10 Pktgen-DPDK&#xff1a;22.04.1 关于DPDK&#xff0c;其实Ubuntu的软件源中就已经包含了最新的Stable版本的DPDK&#xff0c;如果不想自己编译的话&#xff0c;直接 apt install dpdk 也是可以的 安…

python库(17):pkuseg库实现文本分词

1 pkuseg简介 PKUSEG&#xff0c;全称“北京大学语言计算与机器学习研究组开发的分词工具”&#xff0c;它就像一把锋利的瑞士军刀&#xff0c;帮助我们轻松切割文本。 在Python的文本处理领域&#xff0c;有很多分词工具&#xff0c;比如jieba、SnowNLP等。但是&#xff0c;…

iOS多界面传值

iOS多界面传值 文章目录 iOS多界面传值属性传值协议传值Block传值通知传值KVO传值概述使用步骤 总结 属性传值 这个传值方式和他的名字一样&#xff0c;我们主要还是通过属性对值进行一个传递&#xff0c;主要应用场景是前一个页面向后一个页面传值。 首先我们先要设置一个属…

哪里有ai写真软件免费方法?轻松获取写真的5个技巧

想在8月为自己的社交媒体更新个人形象吗&#xff1f;想要为即将到来的秋季增添一抹新意吗&#xff1f; AI写真软件是我们最佳的理想选择&#xff0c;通过简单的操作&#xff0c;我们可以在短时间内获得一张专属于自己的AI头像&#xff0c;让这个夏天的回忆更加生动。 特别是常…

C++进阶:设计模式___适配器模式

前言 在C的基础语法的学习后,更进一步为应用场景多写代码.其中设计模式是有较大应用空间. 引入 原本在写容器中适配器类有关的帖子,发现适配模式需要先了解,于是试着先写篇和适配器模式相关的帖子 理解什么是适配器类,需要知道什么是适配器模式.适配器模式是设计模式的一种.笔…