项目-基于LangChain的ChatPDF系统

问答系统需求文档

一、项目概述

本项目旨在开发一个能够上传 PDF 文件,并基于 PDF 内容进行问答互动的系统。用户可以上传 PDF 文件,系统将解析 PDF 内容,并允许用户通过对话框进行问答互动,获取有关 PDF 文件内容的信息。

二、功能需求

2.1 用户上传 PDF
  • 功能描述:用户可以通过文件选择框上传一个 PDF 文件。
  • 前端需求
    • 提供文件选择框。
    • 显示文件上传进度。
    • 上传成功后显示文件名和上传成功提示。
  • 后端需求
    • 接收并保存用户上传的 PDF 文件。
    • 确保上传的文件格式正确(仅支持 PDF)。
    • 限制文件大小(如最大 50 MB)。
2.2 PDF 内容解析
  • 功能描述:系统解析上传的 PDF 文件内容,将其转换为可处理的文本格式。
  • 前端需求
    • 显示解析进度。
    • 提示用户解析成功或失败。
  • 后端需求
    • 使用 PDF 解析库(如 PyMuPDF、pdfminer)提取 PDF 文本内容。
    • 处理解析错误并返回相应提示。
2.3 用户问答交互
  • 功能描述:用户可以在对话框中输入问题,系统基于解析的 PDF 内容回答问题。
  • 前端需求
    • 提供输入框供用户输入问题。
    • 显示用户问题和系统回答。
  • 后端需求
    • 基于解析的 PDF 内容构建问答模型(如使用 NLP 模型)。
    • 处理用户问题并生成答案。
    • 返回答案给前端显示。

三、接口设计

5.1 上传 PDF 文件接口
  • URL/upload
  • 方法:POST
  • 请求参数
    • file:用户上传的 PDF 文件
  • 响应参数
    • 成功:{ "status": "success", "message": "File uploaded successfully.", "file_id": "unique_file_id" }
    • 失败:{ "status": "error", "message": "File upload failed." }
5.2 提取 PDF 内容接口
  • URL/parse
  • 方法:POST
  • 请求参数
    • file_id:已上传文件的唯一标识符
  • 响应参数
    • 成功:{ "status": "success", "message": "File parsed successfully.", "content": "parsed_content" }
    • 失败:{ "status": "error", "message": "File parsing failed." }
5.3 问答接口
  • URL/ask
  • 方法:POST
  • 请求参数
    • file_id:已上传文件的唯一标识符
    • question:用户输入的问题
  • 响应参数
    • 成功:{ "status": "success", "answer": "answer_to_question" }
    • 失败:{ "status": "error", "message": "Unable to retrieve answer." }

技术实现

系统架构

  1. 前端
    • 文件上传界面
    • 问答交互界面
  2. 后端
    • 文件接收与存储模块
    • PDF 内容解析模块
    • 问答处理模块(基于 LangChain)
  3. 数据库
    • 存储上传文件信息和解析内容

技术栈

  1. 前端
    • HTML/CSS/JavaScript
    • Vue.js
  2. 后端
    • 编程语言:Python
    • 框架:Flask
    • PDF 解析库:PyMuPDF、pdfminer
    • 问答引擎:LangChain
  3. 数据库
    • SQLite

前端实现

安装 Vue CLI

npm install -g @vue/cli
vue create pdf-qa-frontend
cd pdf-qa-frontend

创建组件

src/components 目录下创建 FileUpload.vueQuestionAnswer.vue

FileUpload.vue

<template>
  <div class="upload-container">
    <input type="file" @change="onFileChange" class="file-input" accept=".pdf,.md"/>
    <button @click="uploadFile" class="upload-button">Upload</button>
    <p v-if="message" class="upload-message">{{ message }}</p>
    <div v-if="uploadProgress > 0" class="progress-container">
      <div class="progress-bar" :style="{ width: uploadProgress + '%' }"></div>
      <p>{{ uploadProgress }}%</p>
    </div>
  </div>
</template>

<script>
export default {
  name: 'FileUpload',
  data() {
    return {
      file: null,
      message: '',
      uploadProgress: 0
    };
  },
  methods: {
    onFileChange(event) {
      this.file = event.target.files[0];
    },
    uploadFile() {
      if (!this.file) {
        this.message = 'Please select a file first.';
        return;
      }

      let formData = new FormData();
      formData.append('file', this.file);

      let xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://localhost:5000/upload', true);

      xhr.upload.onprogress = (event) => {
        if (event.lengthComputable) {
          this.uploadProgress = Math.round((event.loaded / event.total) * 100);
        }
      };

      xhr.onload = () => {
        if (xhr.status === 200) {
          let response = JSON.parse(xhr.responseText);
          this.message = response.message;
          this.uploadProgress = 0;
        } else {
          this.message = 'Error uploading file.';
          this.uploadProgress = 0;
        }
      };

      xhr.onerror = () => {
        this.message = 'Error uploading file.';
        this.uploadProgress = 0;
      };

      xhr.send(formData);
    }
  }
};
</script>

<style scoped>
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 20px;
}

.file-input {
  margin-bottom: 10px;
}

.upload-button {
  padding: 8px 16px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.upload-button:hover {
  background-color: #0056b3;
}

.upload-message {
  margin-top: 10px;
  color: #28a745;
}

.progress-container {
  width: 100%;
  max-width: 600px;
  border: 1px solid #ccc;
  border-radius: 4px;
  overflow: hidden;
  margin-top: 10px;
  position: relative;
}

.progress-bar {
  height: 20px;
  background-color: #28a745;
  transition: width 0.4s ease;
}

.progress-container p {
  position: absolute;
  width: 100%;
  text-align: center;
  margin: 0;
  line-height: 20px;
  color: white;
  font-weight: bold;
}
</style>

QuestionAnswer.vue

<template>
  <div class="qa-container">
    <div class="input-container">
      <input
        type="text"
        v-model="question"
        placeholder="Ask a question..."
        @keyup.enter="askQuestion"
        class="question-input"
      />
      <button @click="askQuestion" class="ask-button">Ask</button>
    </div>
    <div class="history-container" v-if="dialogHistory.length">
      <div class="dialog" v-for="(dialog, index) in dialogHistory" :key="index">
        <p><strong>You:</strong> {{ dialog.question }}</p>
        <p><strong>Bot:</strong> {{ dialog.answer }}</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'QuestionAnswer',
  data() {
    return {
      question: '',
      answer: '',
      dialogHistory: [],
      fileId: 'your-file-id'  // Replace with actual file ID after upload
    };
  },
  methods: {
    async askQuestion() {
      if (!this.question) {
        return;
      }

      try {
        let response = await fetch('http://localhost:5000/ask', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            question: this.question,
            file_id: this.fileId
          })
        });
        let result = await response.json();
        this.answer = result.answer;
        this.dialogHistory.push({
          question: this.question,
          answer: this.answer
        });
        this.question = '';
      } catch (error) {
        console.error('Error asking question:', error);
      }
    }
  }
};
</script>

<style scoped>
.qa-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.input-container {
  display: flex;
  width: 100%;
  max-width: 600px;
  margin-bottom: 20px;
}

.question-input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px 0 0 4px;
  font-size: 16px;
}

.ask-button {
  padding: 10px 20px;
  background-color: #28a745;
  color: white;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}

.ask-button:hover {
  background-color: #218838;
}

.history-container {
  width: 100%;
  max-width: 600px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 10px;
  background-color: #f9f9f9;
}

.dialog {
  margin-bottom: 10px;
}

.dialog p {
  margin: 5px 0;
}
</style>

App.vue

<template>
  <div id="app" class="app-container">
    <FileUpload />
    <QuestionAnswer />
  </div>
</template>

<script>
import FileUpload from './components/FileUpload.vue';
import QuestionAnswer from './components/QuestionAnswer.vue';

export default {
  name: 'App',
  components: {
    FileUpload,
    QuestionAnswer
  }
};
</script>

<style>
.app-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
}
</style>

后端实现

安装 Flask 及相关依赖

pip install Flask flask-cors PyMuPDF pdfminer.six semantic-kernel

创建 Flask 应用

在项目根目录下创建 app.py。确保后端 Flask 代码可以正确处理并解析 MD 文件:

import time

import markdown
from flask import Flask, request, jsonify
from flask_cors import CORS
import fitz  # PyMuPDF
import sqlite3

import os

# 加载 .env 到环境变量
from dotenv import load_dotenv, find_dotenv

_ = load_dotenv(find_dotenv())
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import PyPDFLoader

app = Flask(__name__)
CORS(app)
UPLOAD_FOLDER = 'uploads/'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER


@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return jsonify({'status': 'error', 'message': 'No file  part'})
    file = request.files['file']
    if file.filename == '':
        return jsonify({'status': 'error', 'message': 'No selected file'})
    if file:
        filename = file.filename
        file_id = filename  # In a real app, use a unique identifier
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(file_path)

        if filename.endswith('.pdf'):
            content = parse_pdf(file_path)
        elif filename.endswith('.md'):
            content = parse_md(file_path)
        else:
            return jsonify({'status': 'error', 'message': 'Unsupported file type'})

        save_file_to_db(file_id, filename, content)

        return jsonify({'status': 'success', 'message': 'File uploaded and parsed successfully', 'file_id': file_id})

def parse_pdf(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page_num in range(len(doc)):
        page = doc.load_page(page_num)
        text += page.get_text()
    return text

def parse_md(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        text = file.read()
    return markdown.markdown(text)

# Prompt模板
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""

prompt = ChatPromptTemplate.from_template(template)

# 模型
model = ChatOpenAI(model="gpt-4-turbo", temperature=0)

from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough

rag_chain = ()
import tempfile
def save_file_to_db(file_id, filename, content):
     # 加载文档
    # relative_temp_file_path  = os.path.relpath(f"./uploads/{filename}")
    # print(relative_temp_file_path)
    loader = PyPDFLoader(f"./uploads/{filename}")
    pages = loader.load_and_split()

    # 文档切分
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=200,
        chunk_overlap=100,
        length_function=len,
        add_start_index=True,
    )
    texts = text_splitter.create_documents(
        [page.page_content for page in pages[:4]]
    )
    # 灌库
    embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
    db = FAISS.from_documents(texts, embeddings)

    # 检索 top-1 结果
    retriever = db.as_retriever(search_kwargs={"k": 5})

    global rag_chain
    # Chain
    rag_chain = (
            {"question": RunnablePassthrough(), "context": retriever}
            | prompt
            | model
            | StrOutputParser()
    )

@app.route('/ask', methods=['POST'])
def ask_question():
    data = request.json
    file_id = data.get('file_id')
    question = data.get('question')
    print("file_id:", file_id, "question:", question)
    content = get_file_content_from_db(file_id, question)
    print("content:", str(content))
    return jsonify({'status': 'success', 'answer': str(content)})

def get_file_content_from_db(file_id, question):
    result = rag_chain.invoke(question)
    return result

if __name__ == '__main__':
    app.run(debug=True)


运行项目

部署与运行

  1. 前端

    • 运行开发服务器

      npm run serve
      
  2. 后端

    • 运行 Flask 应用

      python app.py
      

实现效果

选择md文件

在这里插入图片描述

上传成功

在这里插入图片描述

问答

  1. Llama 2有多少参数
  2. Llama2Chat有哪些模型参数
  3. TrainingDetails在第几页

在这里插入图片描述

回答基于上传的LIama2.pdf文档。

在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python中的函数递归

函数递归&#xff0c;就是一个函数&#xff0c;自己调用自己。 如上图所示&#xff0c;是一段通过定义函数&#xff0c;编写函数体来实现for循环。实现的是从1到n的累乘。即求n的阶乘&#xff0c; 如上图所示&#xff0c;是一段函数的递归来实现1到n的累乘操作&#xff0c;将1*…

思维,CF1575K - Knitting Batik

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1575K - Knitting Batik 二、解题报告 1、思路分析 诈骗题&#xff0c;上面…

变声器软件免费版有哪些?国内外12大热门变声器大盘点!(新)

变声软件是一种人工智能AI音频处理工具&#xff0c;允许用户实时修改自己的声音或改变预先录制的音频。这些软件解决方案可提供不同的效果&#xff0c;如改变声音的音调或速度&#xff0c;或将我们的声音转换成其他人或其他东西的声音&#xff0c;如名人、卡通人物、机器人或不…

C++开源项目:pathcopycopyV20源码及运行程序

PathCopyCopy 是一个开源的 Windows 资源管理器扩展项目&#xff0c;旨在为用户提供一个更加高效、便捷的文件路径复制和管理工具。以下是关于 PathCopyCopy 开源项目的详细介绍&#xff1a; 1. 项目概述 2. 项目技术分析 3. 项目功能 4. 项目特点 5. 项目应用场景 6. 项目…

记一次源码部分丢失后补救过程

起因 最近植物大战僵尸杂交版玩的入迷&#xff0c;写了一个“神奇”小工具&#xff0c;来辅助游戏。用Git新建一个库&#xff0c;想把代码备份到GitHub&#xff0c;结果push错库了&#xff0c;无奈reset&#xff0c;结果把本地项目一起reset了&#xff0c;结果就是源代码丢失。…

服务器数据恢复—服务器raid5上层zfs文件系统数据恢复案例

服务器数据恢复环境&故障&#xff1a; 一台某品牌X3650M3服务器&#xff0c;服务器中有一组raid5磁盘阵列&#xff0c;上层采用zfs文件系统。 服务器未知原因崩溃&#xff0c;工作人员排查故障后发现服务器的raid5阵列中有两块硬盘离线导致该阵列不可用&#xff0c;服务器内…

springboot报错:Failed to start bean ‘documentationPluginsBootstrapper‘

项目场景&#xff1a; springboot项目启动时报错 问题描述 具体报错信息&#xff1a; 可能原因分析&#xff1a; 1、SpringFox的版本与Spring Boot的版本不兼容。解决这个问题&#xff0c;你可能需要检查你正在使用的SpringFox和Spring Boot的版本&#xff0c;确保它们是兼容…

Python01 -分解整包数据到各个变量操作和生成器

Python 的星号表达式可以用来解决这个问题。比如&#xff0c;你在学习一门课程&#xff0c;在学期末的时候&#xff0c;你想统计下家庭作业的平均成绩&#xff0c;但是排除掉第一个和最后一个分数。如果只有四个分数&#xff0c;你可能就直接去简单的手动赋值&#xff0c;但如果…

Java Web学习笔记24——Vue项目开发流程

import是引入文件。 export是将对象导出为模块。 new Vue({ router, router: h > h(App) }).$mount(#app) App.vue: vue的组成文件以.vue结尾&#xff0c;每个组件由三个部分组成&#xff1a;<template>、<script>、<style>。 <template><d…

【MMU】——页表映射示例

文章目录 页表映射示例一级页表二级页表二级页表的优势页表映射示例 一级页表 上图一级页表中假设以 4KB 物理页为映射单位,一个进程 4GB 的虚拟地址空间需要:4GB/4KB = 1MB 个页表项,每个页表项目占用 4 个字节所以每个一级页表需要 4MB 的存储空间,每个进程需要 4MB 的内…

【教学类-36-07】20240608动物面具(通义万相)-A4大小7图15手工纸1图

背景需求&#xff1a; 风变的AI对话大师一年到期了&#xff0c;也没有看到续费的按钮。不能使用它写代码了。 MJ早就用完了&#xff0c;最后480次&#xff0c;我担心信息课题会用到它生图&#xff0c;所以不敢用。 最近探索其他类似MJ的免费出图工具——找到了每天给50张免费图…

Django 连接mysql数据库配置

1&#xff0c;提前创建注册的app1应用 Test/Test/settings.py python manage.py startapp app1 2&#xff0c;配置mysql数据库连接 Test/Test/settings.py DATABASES {default: {ENGINE: django.db.backends.mysql,# 数据库名字NAME: db1,# 连接mysql数据库用户名USER: ro…

如何在Coze中实现Bot对工作流的精准调用(如何提高Coze工作流调用的准确性和成功率)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 工作流(workflow)📒📝 创建设计工作流📝 添加工作流📝 调用工作流⚓️ 相关链接 ⚓️📖 介绍 📖 在使用Coze平台创建智能Bot时,您可能会遇到一个常见问题:即便添加了正确的工作流,Bot却没有按照预期调用它们。…

国产操作系统上给virtualbox中win7虚拟机安装增强工具 _ 统信 _ 麒麟 _ 中科方德

原文链接&#xff1a;国产操作系统上给virtualbox中win7虚拟机安装增强工具 | 统信 | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇在国产操作系统上给win7虚拟机安装virtualbox增强工具的文章。VirtualBox增强工具&#xff08;Guest Additions&a…

45-2 waf绕过 - XSS 绕过WAF方法

环境准备: 43-5 waf绕过 - 安全狗简介及安装-CSDN博客然后安装pikachu靶场:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客打开pikachu靶场 http://127.0.0.1/pikachu-master/vul/xss/xss_reflected_get.php 使用常见payload被安全狗拦截…

【Python】解决Python报错:AttributeError: ‘list‘ object has no attribute ‘shape‘

​​​​ 文章目录 引言1. 错误详解2. 常见的出错场景2.1 使用列表代替NumPy数组2.2 错误的数据类型转换 3. 解决方案3.1 确保使用NumPy数组3.2 检查数据类型 4. 预防措施4.1 使用类型注解4.2 编写单元测试 结语 引言 在使用Python进行数据处理或科学计算时&#xff0c;可能会…

操作系统的启动过程和初始化

参考来源&#xff1a; Linux的启动过程&#xff0c;作者&#xff1a;阮一峰 第一步、加载内核 操作系统接管硬件以后&#xff0c;首先读入 /boot 目录下的内核文件。 rootub1804:/boot# ls -l 总用量 120636 -rw-r--r-- 1 root root 237767 5月 19 2023 config-5.4.0-15…

【C语言】冒泡排序(经典算法,干货满满!!!)

目录 前言1、原理2、冒泡排序的应用3、对冒泡排序的应用的优化4、冒泡排序适用于以下情况 前言 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单直观的排序算法&#xff0c;它重复地遍历要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它…

嵌入式学习——4——C++中的动态内存分配和回收(堆区)

1、内存的分配与回收 C语言中使用的是malloc和free函数进行动态内存分配和回收的。 C中依然可以使用上述的两个函数来完成动态内存分配和回收的。 C也给用户提供了两个关键字new、delete来完成动态内存分配和回收的 单个分配、回收 //在堆区申请了int类型的大小空间&#xff0c…

Java面试八股之组合、聚合和关联三者的区别是什么

组合、聚合和关联三者的区别是什么 关联&#xff08;Association&#xff09;: 最基本的一种关系&#xff0c;表示一个类知道另一个类的存在&#xff0c;或者说是类之间的某种联系。 关联可以是双向的也可以是单向的&#xff0c;且不规定参与关联的对象的生存周期。 实例&a…