项目-基于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"><inputtype="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 timeimport markdown
from flask import Flask, request, jsonify
from flask_cors import CORS
import fitz  # PyMuPDF
import sqlite3import 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 PyPDFLoaderapp = 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.filenamefile_id = filename  # In a real app, use a unique identifierfile_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 textdef 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 RunnablePassthroughrag_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# Chainrag_chain = ({"question": RunnablePassthrough(), "context": retriever}| prompt| model| StrOutputParser())@app.route('/ask', methods=['POST'])
def ask_question():data = request.jsonfile_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 resultif __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文档。

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

excel 插入图片不变形的方法

在单元格内插入形状&#xff0c; 设置图片格式 设置图片的高宽

【Linux进程篇】Linux中的等待机制与替换策略

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 目录 ​编辑 进程等待 进程等待必要性 进程等待的方法 wait方法 waitpid方法 获取子进程status 阻塞与非阻塞 进程程序替换 替换原理 替换函数 进程等待 进程等待必要性 之前讲过&#xff0c;子进程退出&am…

傲医医疗集成引擎 Rhapsody 在超融合信创平台表现如何?

作者&#xff1a;SmartX 商业团队 黄玉辉 随着越来越多的医疗用户基于超融合基础设施实现 IT 基础架构信创转型&#xff0c;超融合信创架构在医疗业务场景中的实际表现也得到更多关注。尤其是集成平台业务场景——作为三甲医院互联互通评级中不可缺少的核心业务系统&#xff0…

“云环境下的等保测评新趋势:混合云安全策略与合规性考量“

在云环境尤其是混合云场景下&#xff0c;等保测评&#xff08;信息安全等级保护测评&#xff09;的新趋势聚焦于更加灵活、高效且全面的安全策略&#xff0c;以及确保合规性。以下是几个关键点&#xff1a; 混合云安全策略的新趋势&#xff1a; 1. 统一安全管理&#xff1a;由…

概率分布、回归分析、假设检验……用 DolphinDB 函数库快速实现概率统计分析

在金融和物联网等领域&#xff0c;概率统计与分析扮演着至关重要的角色。DolphinDB 作为一款强大的时序数据库&#xff0c;提供了一系列内置的概率统计与分析函数&#xff0c;能够满足用户的各种需求。 金融领域 风险管理&#xff1a;通过概率统计分析&#xff0c;金融机构可…

【主题广泛|投稿优惠】2024年交通运输与信息科学国际会议(ICTIS 2024)

2024年交通运输与信息科学国际会议&#xff08;ICTIS 2024&#xff09; 2024 International Conference on Transportation and Information Science 【重要信息】 大会地点&#xff1a;青岛 大会官网&#xff1a;http://www.icictis.com 投稿邮箱&#xff1a;icictissub-conf.…

【Vue】成绩案例

文章目录 一、功能描述二、思路分析三、完整代码 一、功能描述 1.渲染功能 2.删除功能 3.添加功能 4.统计总分&#xff0c;求平均分 二、思路分析 渲染功能 v-for :key v-bind:动态绑定class的样式&#xff08;来回切换&#xff09; 删除功能 v-on绑定事件&#xff0c; 阻止…

线性代数|机器学习-P2 A的列向量空间

文章目录 1. Ax矩阵的形式2. ACR 矩阵分解2.1 rank1 矩阵分解2.2 rank2 矩阵分解2.3 ACMR,求M 3. Ax 向量 1. Ax矩阵的形式 假设我们有如下矩阵A&#xff1a; A x [ 2 1 3 3 1 4 5 7 12 ] [ x 1 x 2 x 3 ] \begin{equation} Ax\begin{bmatrix} 2&1&3\\\\ 3&1&am…

【微信小程序开发】小程序中的上滑加载更多,下拉刷新是如何实现的?

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

期望20K,2年golang深圳某互联网小公司一面

后续约了二面&#xff08;CTO面&#xff09;&#xff0c;需要到现场&#xff0c;基本没问啥具体的技术知识&#xff0c;都是聊规划和个人职业目标 一面 1、假设访问百度网站&#xff0c;从在浏览器输入网址&#xff0c;到最终页面展示出来&#xff0c;中间会发生哪些事情&…

使用Python和wxPython将PNG文件转换为JPEG文件

简介&#xff1a; 在图像处理中&#xff0c;有时候我们需要将PNG格式的图像文件转换为JPEG格式。本篇博客将介绍如何使用Python编程语言和wxPython图形用户界面库&#xff0c;以及Pillow图像处理库来实现这一转换过程。通过本文的指导&#xff0c;您将学习如何快速将指定文件夹…

30分钟快速上手scapy

在前两篇博客中&#xff0c;笔者简单分享了如何使用scapy进行网络流量的分析&#xff0c;但比较粗略&#xff0c;没有详细说明&#xff0c;本文我们就0开始&#xff0c;一起快速学习一下scapy 框架的使用。 另附之前的两篇博客&#xff0c;有需要快速参考代码的请自取&#xf…

外贸如何找国外客户?

外贸客户开发国外客户可以使用多种软件来完成&#xff0c;以下是一些常用的软件推荐&#xff1a; 即时通讯软件&#xff1a; SKYPE&#xff1a;一款功能丰富的即时通讯软件&#xff0c;支持视频聊天、多人语音会议、文件传输等&#xff0c;方便与海外客户进行实时沟通。Line&a…

用增之Firebase

目录 简介 开发准备&#xff1a; 1、在Firebase平台创建项目 2、将项目关联到应用 3、项目配置 简介 前面讲了google ddl部分&#xff0c;本篇为Firebase的事件上报部分&#xff0c;包括在FireBase平台创建应用 &#xff0c; 如果有用到ddl…

【网络安全的神秘世界】JavaScript

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 01-JavaScript简介 JavaScript背景 2003年之前&#xff0c;JavaScript被称为“牛皮藓”&#xff0c;用来制作页面上的广告、弹窗、漂浮的广告 2004年&a…

小猪APP分发:让APP封装变得如此简单

你是否曾经在开发完一款APP后&#xff0c;为了封装、分发而头疼不已&#xff1f;别担心&#xff0c;小猪APP分发来拯救你了&#xff01;这款神器不仅能让你的工作变得更加高效&#xff0c;还能让你的APP在各大平台上顺利分发。 小猪APP封装www.ppzhu.net APP封装的挑战 开发一…

docker 停止重启容器命令start/stop/restart详解(容器生命周期管理教程-2)

Docker 提供了多个命令来管理容器的生命周期&#xff0c; 其中start、stop 和 restart。这些命令允许用户控制容器的运行状态。 1. docker start 命令格式&#xff1a; docker start [OPTIONS] CONTAINER [CONTAINER...]功能&#xff1a; 启动一个或多个已经停止的 Docker …

WLAN工作原理

目录 一、引言二、FAT AP的工作模式与挑战三、FIT APAC的组网方式四、CAPWAPI隧道技术五、WLAN网络的关键报文六、漫游机制七、总结 一、引言 随着无线网络的迅猛发展&#xff0c;WLAN&#xff08;无线局域网&#xff09;技术已广泛应用于各个领域&#xff0c;为企业和个人提供…

若依 ruoyi-vue element-ui el-cascader 级联选择器 选择任意一级选项,去掉单选按钮,选中点击后隐藏

Cascader 级联选择器 选择任意一级选项&#xff0c;去掉单选按钮。 这兄弟文章写的可以&#xff0c;查了一堆文章&#xff0c;基本搞完才发现。 官方的例子不支持选中后自动关闭&#xff0c;要点击旁边空白&#xff0c;并且单选框太小了。 <el-form-item label"所属地…

基于SSM+Jsp的二手手机回收平台系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…