LLamaIndex - 构建全栈Web应用程序的指南

本文翻译整理自:A Guide to Building a Full-Stack Web App with LLamaIndex
https://docs.llamaindex.ai/en/stable/understanding/putting_it_all_together/apps/fullstack_app_guide/

文章目录

    • 简介
    • 一、Flask 后端
      • 基本Flask - 处理用户索引查询
      • Advanced Flask - 处理用户文档上传
        • `index_server.py`
        • `index_server.py`
        • `flask_demo.py`
    • 二、React前端
      • fetchDocuments.tsx
      • queryIndex.tsx
      • insertDocument.tsx
      • 所有其他前端好
    • 结论


简介

LlamaIndex是一个python库,这意味着将其与全栈Web应用程序集成将与您可能习惯的有点不同。

本指南旨在介绍创建用python编写的基本API服务所需的步骤,以及它如何与TypeScript+React前端交互。

这里的所有代码示例都可以从flask_react文件夹中的llama_index_starter_pack获得。

本指南中使用的主要技术如下:

  • python 3.11
  • llama_index
  • Flask
  • typescript
  • React

一、Flask 后端

对于本指南,我们的后端将使用FlaskAPI服务器与我们的前端代码通信。如果您愿意,您也可以轻松地将其转换为FastAPI服务器或您选择的任何其他python服务器库。

使用Flask设置服务器很容易。您导入包,创建应用程序对象,然后创建端点。让我们先为服务器创建一个基本框架:

from flask import Flaskapp = Flask(__name__)@app.route("/")
def home():return "Hello World!"if __name__ == "__main__":app.run(host="0.0.0.0", port=5601)

flask_demo.py

如果您运行此文件(python flask_demo.py),它将在端口5601上启动服务器。如果您访问http://localhost:5601/,您将看到浏览器中呈现的“Hello World!”文本。不错!

下一步是决定我们想要在服务器中包含哪些函数,并开始使用LlamaIndex。

为了简单起见,我们可以提供的最基本操作是查询现有索引。使用LlamaIndex的paul graham文章,创建一个文档文件夹并下载+放置其中的文章文本文件。


基本Flask - 处理用户索引查询

现在,让我们编写一些代码来初始化我们的索引:

import os
from llama_index.core import (SimpleDirectoryReader,VectorStoreIndex,StorageContext,load_index_from_storage,
)# NOTE: for local testing only, do NOT deploy with your key hardcoded
os.environ["OPENAI_API_KEY"] = "your key here"index = Nonedef initialize_index():global indexstorage_context = StorageContext.from_defaults()index_dir = "./.index"if os.path.exists(index_dir):index = load_index_from_storage(storage_context)else:documents = SimpleDirectoryReader("./documents").load_data()index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)storage_context.persist(index_dir)

这个函数将初始化我们的索引。如果我们在main函数中启动Flask 服务器之前调用它,那么我们的索引将准备好供用户查询!

我们的查询端点将接受以查询文本作为参数的GET请求。完整的端点函数如下所示:

from flask import request@app.route("/query", methods=["GET"])
def query_index():global indexquery_text = request.args.get("text", None)if query_text is None:return ("No text found, please include a ?text=blah parameter in the URL",400,)query_engine = index.as_query_engine()response = query_engine.query(query_text)return str(response), 200

现在,我们为我们的服务器引入了一些新概念:

  • 由函数装饰器定义的新/query端点
  • 来自Flask 的新导入request,用于从请求中获取参数
  • 如果缺少text参数,则返回错误消息和适当的 HTML 响应代码
  • 否则,我们查询索引,并将响应作为字符串返回

您可以在浏览器中测试的完整查询示例可能如下所示:http://localhost:5601/query?text=what did the author do growing up(一旦您按回车键,浏览器将把空格转换为“%20”字符)。

事情看起来很好!我们现在有了一个功能性API。使用您自己的文档,您可以轻松地为任何应用程序提供一个接口来调用Flask API并获得查询答案。


Advanced Flask - 处理用户文档上传

事情看起来很酷,但我们如何更进一步?如果我们想让用户通过上传自己的文档来构建自己的索引怎么办?别害怕,Flask可以处理这一切:肌肉:。

为了让用户上传文档,我们必须采取一些额外的预防措施。索引将变为可变的,而不是查询现有索引。如果您有许多用户添加到同一个索引,我们需要考虑如何处理并发。我们的Flask服务器是线程化的,这意味着多个用户可以使用将同时处理的请求ping服务器。

一种选择可能是为每个用户或组创建一个索引,并从S3存储和获取内容。但是对于这个例子,我们将假设有一个用户正在与之交互的本地存储索引。

为了处理并发上传并确保顺序插入到索引中,我们可以使用BaseManagerpython包来使用单独的服务器和锁提供对索引的顺序访问。这听起来很可怕,但也没那么糟糕!我们将把所有索引操作(初始化、查询、插入)移动到BaseManager“index_server”中,它将从我们的Flask服务器调用。

下面是我们移动代码后index_server.py的基本示例:


index_server.py
import os
from multiprocessing import Lock
from multiprocessing.managers import BaseManager
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Document# NOTE: for local testing only, do NOT deploy with your key hardcoded
os.environ["OPENAI_API_KEY"] = "your key here"index = None
lock = Lock()def initialize_index():global indexwith lock:# same as before ...passdef query_index(query_text):global indexquery_engine = index.as_query_engine()response = query_engine.query(query_text)return str(response)if __name__ == "__main__":# init the global indexprint("initializing index...")initialize_index()# setup server# NOTE: you might want to handle the password in a less hardcoded waymanager = BaseManager(("", 5602), b"password")manager.register("query_index", query_index)server = manager.get_server()print("starting server...")server.serve_forever()

index_server.py

因此,我们移动了我们的函数,引入了Lock对象,它确保了对全局索引的顺序访问,在服务器中注册了我们的单个函数,并在端口5602上使用密码password启动了服务器。

然后,我们可以如下调整我们的Flask 代码:

from multiprocessing.managers import BaseManager
from flask import Flask, request# initialize manager connection
# NOTE: you might want to handle the password in a less hardcoded way
manager = BaseManager(("", 5602), b"password")
manager.register("query_index")
manager.connect()@app.route("/query", methods=["GET"])
def query_index():global indexquery_text = request.args.get("text", None)if query_text is None:return ("No text found, please include a ?text=blah parameter in the URL",400,)response = manager.query_index(query_text)._getvalue()return str(response), 200@app.route("/")
def home():return "Hello World!"if __name__ == "__main__":app.run(host="0.0.0.0", port=5601)

flask_demo.py

两个主要变化是连接到我们现有的BaseManager服务器并注册函数,以及通过/query端点中的管理器调用函数。

需要特别注意的是,BaseManager服务器不会像我们预期的那样返回对象。要将返回值解析为原始对象,我们调用_getvalue()函数。

如果我们允许用户上传他们自己的文档,我们可能应该从文档文件夹中删除Paul Graham的文章,所以让我们先这样做。然后,让我们添加一个端点来上传文件!首先,让我们定义我们的Flask端点函数:

...
manager.register("insert_into_index")
...@app.route("/uploadFile", methods=["POST"])
def upload_file():global managerif "file" not in request.files:return "Please send a POST request with a file", 400filepath = Nonetry:uploaded_file = request.files["file"]filename = secure_filename(uploaded_file.filename)filepath = os.path.join("documents", os.path.basename(filename))uploaded_file.save(filepath)if request.form.get("filename_as_doc_id", None) is not None:manager.insert_into_index(filepath, doc_id=filename)else:manager.insert_into_index(filepath)except Exception as e:# cleanup temp fileif filepath is not None and os.path.exists(filepath):os.remove(filepath)return "Error: {}".format(str(e)), 500# cleanup temp fileif filepath is not None and os.path.exists(filepath):os.remove(filepath)return "File inserted!", 200

还不错!你会注意到我们将文件写入磁盘。如果我们只接受基本的文件格式,如txt文件,我们可以跳过这一点,但是写入磁盘,我们可以利用LlamaIndex的SimpleDirectoryReader来处理一堆更复杂的文件格式。或者,我们还使用第二个POST参数来使用文件名作为doc_id或者让LlamaIndex为我们生成一个。一旦我们实现前端,这将更有意义。

对于这些更复杂的请求,我还建议使用像Postman这样的工具。使用postman测试端点的示例在这个项目的存储库中。

最后,您会注意到我们为Manager添加了一个新函数。让我们在index_server.py中实现它:

def insert_into_index(doc_text, doc_id=None):global indexdocument = SimpleDirectoryReader(input_files=[doc_text]).load_data()[0]if doc_id is not None:document.doc_id = doc_idwith lock:index.insert(document)index.storage_context.persist()...
manager.register("insert_into_index", insert_into_index)
...

简单!如果我们启动index_server.pyflask_demo.py的python文件,我们就有了一个Flask API服务器,可以处理多个请求,将文档插入矢量索引并响应用户查询!

为了支持前端的一些功能,我调整了Flask API的一些响应,并添加了一些功能来跟踪哪些文档存储在索引中(LlamaIndex目前不支持用户友好的方式,但我们可以自己增强它!)。最后,我必须使用Flask-corspython包向服务器添加CORS支持。

查看存储库中的完整flask_demo.pyindex_server.py脚本以获取最后的小更改、requirements.txt文件和示例Dockerfile以帮助部署。


二、React前端

一般来说,React和Typescript是当今编写webapp最流行的库和语言之一。本指南将假定您熟悉这些工具的工作原理,因为否则本指南的长度将增加三倍:微笑:。

在存储库中,前端代码被组织在react_frontend文件夹内。

前端最相关的部分将是src/apis文件夹。这是我们调用Flask服务器的地方,支持以下查询:

  • /query–对现有索引进行查询
  • /uploadFile–将文件上传到Flask 服务器以插入索引
  • /getDocuments–列出当前文档标题及其部分文本

使用这三个查询,我们可以构建一个健壮的前端,允许用户上传和跟踪他们的文件、查询索引、查看查询响应以及有关使用哪些文本节点来形成响应的信息。


fetchDocuments.tsx

您猜对了,该文件包含获取索引中当前文档列表的函数。代码如下:

export type Document = {id: string;text: string;
};const fetchDocuments = async (): Promise<Document[]> => {const response = await fetch("http://localhost:5601/getDocuments", {mode: "cors",});if (!response.ok) {return [];}const documentList = (await response.json()) as Document[];return documentList;
};

如您所见,我们对Flask服务器进行了查询(这里,它假定在localhost上运行)。请注意,我们需要包含mode: 'cors'选项,因为我们正在发出外部请求。

然后,我们检查响应是否可以,如果可以,则获取响应json并返回它。在这里,响应json是在同一文件中定义的Document对象的列表。


queryIndex.tsx

该文件将用户查询发送到Flask 服务器,并取回响应,以及有关我们索引中哪些节点提供了响应的详细信息。

export type ResponseSources = {text: string;doc_id: string;start: number;end: number;similarity: number;
};export type QueryResponse = {text: string;sources: ResponseSources[];
};const queryIndex = async (query: string): Promise<QueryResponse> => {const queryURL = new URL("http://localhost:5601/query?text=1");queryURL.searchParams.append("text", query);const response = await fetch(queryURL, { mode: "cors" });if (!response.ok) {return { text: "Error in query", sources: [] };}const queryResponse = (await response.json()) as QueryResponse;return queryResponse;
};export default queryIndex;

这类似于fetchDocuments.tsx文件,主要区别在于我们将查询文本作为参数包含在URL中。然后,我们检查响应是否正常,并使用适当的typescript类型返回它。


insertDocument.tsx

可能最复杂的API调用是上传文档。这里的函数接受一个文件对象,并使用FormData构造一个POST请求。

实际的响应文本未在应用程序中使用,但可用于就文件是否上传失败提供一些用户反馈。

const insertDocument = async (file: File) => {const formData = new FormData();formData.append("file", file);formData.append("filename_as_doc_id", "true");const response = await fetch("http://localhost:5601/uploadFile", {mode: "cors",method: "POST",body: formData,});const responseText = response.text();return responseText;
};export default insertDocument;

所有其他前端好

这几乎结束了前端部分!React前端代码的其余部分是一些非常基本的React组件,我最好的尝试是让它看起来至少有点漂亮:微笑:。

我鼓励阅读代码库的其余部分并提交任何PR以进行改进!


结论

本指南涵盖了大量信息。我们从一个用python编写的基本“Hello World”Flask服务器,到一个功能齐全的LlamaIndex驱动的后端,以及如何将其连接到前端应用程序。

如您所见,我们可以轻松地增强和包装LlamaIndex提供的服务(如小型外部文档跟踪器),以帮助在前端提供良好的用户体验。

您可以利用这一点并添加许多功能(多索引/用户支持、将对象保存到S3、添加松果矢量服务器等)。阅读本文后构建应用程序时,请务必在Discord中分享最终结果!祝你好运!


2024-09-28 译

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

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

相关文章

web前端(本地存储问题超过5MB不继续保存解决办法)

及时使用pinia进行持久化存储&#xff0c;存入localstorage仍然会有超过5MB就不再处理保存的问题 解决办法&#xff1a;取消使用pinia-plugin-persistedstate持久化插件&#xff0c;使用localforage&#xff0c;pinia库正常开启persist: true localForage 是一个快速而简单的…

cocos打包后发布web,控制台报错.plist资源下载404

web加载报错 download failed: assets/main/native/0a/0a1a5e41-7d91-4a5d-9552-2c10e5fc5867.plist, status: 404&#xff0c; 应该是MIME属性没有设置允许下载.plist后缀的文件。 对于linux应该改nginx或apache&#xff0c;允许下载该类文件。 我部署在了windows服务器上&am…

【微服务即时通讯系统】——etcd一致性键值存储系统、etcd的介绍、etcd的安装、etcd使用和功能测试

文章目录 etcd1. etcd的介绍1.1 etcd的概念 2. etcd的安装2.1 安装etcd2.2 安装etcd客户端C/C开发库 3. etcd使用3.1 etcd接口介绍 4. etcd使用测试4.1 原生接口使用测试4.2 封装etcd使用测试 etcd 1. etcd的介绍 1.1 etcd的概念 Etcd 是一个基于GO实现的 分布式、高可用、一致…

计算机毕业设计 基于协同过滤算法的个性化音乐推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Redis实战--Redis的数据持久化与搭建Redis主从复制模式和搭建Redis的哨兵模式

Redis作为一个高性能的key-value数据库&#xff0c;广泛应用于缓存、消息队列、排行榜等场景。然而&#xff0c;Redis是基于内存的数据库&#xff0c;这意味着一旦服务器宕机&#xff0c;内存中的数据就会丢失。为了解决这个问题&#xff0c;Redis提供了数据持久化的机制&#…

深入解析Excel文件格式:.xls与.xlsx的差异与应用指南

在当今的数据处理和办公自动化领域&#xff0c;Microsoft Excel 无疑是一款极为重要的工具。 它不仅广泛应用于日常的数据录入、计算和图表制作&#xff0c;而且也是数据分析、财务建模等专业 领域不可或缺的软件。Excel 的文件格式经历了多个版本的迭代&#xff0c;其中 .xl…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,1-2

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…

二叉树进阶oj题【二叉树相关10道oj题的解析和c++代码实现】

目录 二叉树进阶oj题1.根据二叉树创建字符串2.二叉树的层序遍历3.二叉树的层序遍历 II4.二叉树的最近公共祖先5.二叉搜索树和双向链表6.从前序与中序遍历序列构造二叉树7.从中序和后序遍历序列来构造二叉树8.二叉树的前序遍历&#xff0c;非递归迭代实现9.二叉树中序遍历 &…

部标主动安全(ADAS+DMS)对接说明

1.前言 上一篇介绍了部标&#xff08;JT/T1078&#xff09;流媒体对接说明&#xff0c;这里说一下如何对接主动安全附件服务器。 流媒体的对接主要牵扯到4个方面&#xff1a; &#xff08;1&#xff09;平台端&#xff1a;业务端系统&#xff0c;包含前端呈现界面。 &#x…

博弈论(学习笔记)

定义何为最优&#xff01; 最优解是均衡&#xff01;&#xff08;&#xff09; 一次博弈 --- 一面之缘 复杂动态博弈&#xff1b; 路怒症----陌生人&#xff0c;一次性博弈。 一次性博弈最能暴露人性。 重复博弈太压抑了。 沙普利求解合作博弈的著名理论---如何为参与者制定利益…

GDAL Unable to open EPSG support file gcs.csv

python环境从3.6升级到3.7&#xff0c;gdal版本从2.2.4升级到3.4.1之后&#xff0c;执行原来的gdal脚本&#xff0c;结果报出如下错误 ”ERROR 4: Unable to open EPSG support file gcs.csv. Try setting the GDAL_DATA environment variable to point to the directory conta…

Win10 QT 配置Android开发环境-jdk/sdk/gradle

原文链接&#xff1a;QT 配置Android开发环境-jdk/sdk/gradle 可用配置: QT-6.5.36.6.3 ndk-25.X 内置sdk 自动下载的api 和build tools platform tools gradle-只能要什么版本下什么 总之一句话:出现问题直接换QT 前言 QT开发android是可行的,QT拥有非常强的跨平台能力,能使…

python画图|自制渐变柱状图

在前述学习过程中&#xff0c;我们已经通过官网学习了如何绘制渐变的柱状图及其背景。 掌握一门技能的最佳检验方式就是通过实战&#xff0c;因此&#xff0c;本文尝试做一些渐变设计。 前述学习记录可查看链接&#xff1a; Python画图|渐变背景-CSDN博客 【1】柱状图渐变 …

AI产品经理PRD文档与传统产品经理PRD有什么不同呢?

目录 模型输出&#xff1a;说白了&#xff0c;就是你的AI要干啥数据接入&#xff1a;你的AI要吃啥“粮食”验收标准&#xff1a;怎么判断你的AI干得好不好经验总结 你好&#xff0c;我是三桥君 在工作中&#xff0c;当我作为传统产品经理时&#xff0c;通常只需提供产品需求文…

【内网渗透】最保姆级的春秋云镜Flarum打靶笔记

目录 flag1 flag3 flag4​ flag2 flag1 扫外网 打的是flarum论坛&#xff0c;p牛之前有写过phar反序列化的利用&#xff1a; 从偶遇Flarum开始的RCE之旅 rockyou.txt爆出administrator/1chris&#xff0c;登录 用这个工具生成phar包 https://github.com/ambionics/p…

面试经验分享

作为一个面试过别人很多次&#xff0c;同时自己也面试过很多次的人&#xff0c;今天来给大家分享一些踩雷的经验 1.面试地点&#xff0c;有人会问了为啥上来就分享面试地点尼&#xff0c;因为面试地点就决定了你的工作位置&#xff0c; 你不可能说当个工厂IT&#xff0c;但是…

详解mysql和消息队列数据一致性问题

目录 前言 保持系统数据同步&#xff08;双写问题&#xff09; 消息队列消息丢失的问题 总结 前言 在当今互联网飞速发展的时代&#xff0c;随着业务复杂性的不断增加&#xff0c;消息队列作为一种重要的技术手段&#xff0c;越来越多地被应用于各种场景。它们不仅能有效解…

项目:微服务即时通讯系统客户端(基于C++QT)]四,中间界面搭建和逻辑准备

四&#xff0c;中间界面搭建 前言:当项目越来越复杂的时候&#xff0c;或许画草图是非常好的选择 一&#xff0c;初始化中间窗口initMidWindow void mainWidget::initMidWindow() {//使用网格布局进行管理QGridLayout* layout new QGridLayout();//距离上方 20px 的距离&…

Unity3D Compute Shader同步详解

在Unity3D中&#xff0c;Compute Shader是一种强大的工具&#xff0c;它利用GPU的并行处理能力来执行复杂的计算任务&#xff0c;从而减轻CPU的负担&#xff0c;提高游戏的性能和效率。然而&#xff0c;由于GPU的工作方式&#xff0c;对共享资源的访问需要特别注意同步问题&…

Arthas redefine(加载外部的.class文件,redefine到JVM里 )

文章目录 二、命令列表2.2 class/classloader相关命令2.2.3 redefine&#xff08;加载外部的.class文件&#xff0c;redefine到JVM里 &#xff09;举例1&#xff1a;加载新的代码&#xff0c;jad/mc 命令使用举例2&#xff1a;上传 .class 文件到服务器的技巧 本人其他相关文章…