Quivr 基于GPT和开源LLMs构建本地知识库 (更新篇)

技术

点击上方蓝字关注我们

picture.image

一、前言

自从大模型被炒的越来越火之后,似乎国内涌现出很多希望基于大模型构建本地知识库的需求,大概在5月底的时候,当时 Quivr 发布了第一个0.0.1版本,第一个版本仅仅只是使用LangChain技术结合OpenAI的GPT模型实现了一个最基本的架子,功能并不够完善,但可以研究研究思路,当时 Quivr 通过借助于GPT的模型能力,选择Supabase构建向量数据库来实现个人知识库还算是一个不错的选择,自此一直有在关注 Quivr 的进展,基本上Quivr的更新频率还是比较高的,5月底写了一篇关于如何在本地基于Quivr构建知识库的文章之后,陆陆续续基本上都有一些朋友私聊询问有关Quivr构建的一些问题,也有一些对于Quivr未来功能规划方向的建议和期望,如果Quivr发展的比较成熟,对于个人或者中小企业或许也是一个低成本的选择。

随着这两个多月的更新, Quivr已经陆续发布了五十多个版本 ,不管是对原来功能的改进,代码的重构,还是扩展了很多新功能,都让Quivr看起来没有原来那么弱小了,基础的功能基本上也覆盖到了。感兴趣的可以尝试一下。

对于原来发布的文章和视频,有感兴趣的可以从下面的链接进去,因为Quivr一直在更新,在部署方面可能有些许变化,如果想部署最新版本的Quivr,可以直接看这篇最新的升级篇即可。

picture.image

二、功能特性

2.1、大脑扩展能力

从单个账号只支持一个大脑,到现在可以支持多个大脑(具体数量可以配置,默认为5个),这样部署一套Quivr系统就可以创建多个大脑来对知识库进行分开维护,减少数据的检索范围和数据权限隔离。

用户可以根据偏好来自定义知识库,比如针对产品的智能客服、针对交付的Q&A助理、产品经理助手等等。

2.2、大脑权限控制

支持对单个知识库根据[浏览]、[编辑]、[所有者]三个角色来设置对应的访问权限,同时也支持通过链接和邮件的方式分享个人大脑给其他用户。

这样就可以很方便的实现个人私有知识库,或者是公司团队共享的知识库,而避免了以前每个用户都需要重复上传相同的知识,导致Key的浪费和知识的冗余。

2.3、LLM扩展能力

原来的版本只支持集成GPT和Claude模型,现在扩展了对本地开源模型的支持,如GPT4All,后续还将支持更多的开源模型。

2.4、开放API接口

Quivr采用前后端分离的独立架构,Quivr 使用 FastAPI 为后端提供 RESTful API,后端服务可以独立使用,不需要前端应用程序,我们的第三方应用也可以很方便的通过API接口集成Quivr大脑的我们自己的产品中

picture.image

三、基础环境准备

3.1、先决条件

为了减少部署过程中不必要的麻烦,建议操作系统选择 Ubuntu 22或更高版本 ,至于服务器只要能正常访问OpenAI的接口都可以,我在GCP/AWS/阿里云上都安装过,主要解决网络问题,选对服务器所在区域即可。

系统内存:如果只是个人用来部署玩一下,建议不少于1GB,2GB比较合适,如果想用于正式环境,则需根据具体的业务访问量配置。

系统硬盘:仅仅部署演示,建议不少于30GB。

接下来将演示在 Ubuntu 22 版本 上快速部署Quivr来构建本地知识库系统。

3.2、安装Docker & Docker-Compose

picture.image

首先安装 Docker 和 Docker Compose ,可以按照以下步骤进行操作:

1、更新系统软件包列表:


      
          

          **sudo apt update** 
      
    

2、安装Docker依赖的软件包:


      
          

          **sudo apt install apt-transport-https ca-certificates curl software-properties-common** 
      
    

3、添加Docker官方的GPG密钥:


      
          

          **curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg** 
      
    

4、添加Docker的软件源:


      
          

          **echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb\_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null** 
      
    

5、更新软件包列表:


      
          

          **sudo apt update** 
      
    

6、安装Docker Engine:


      
          

          **sudo apt install docker-ce docker-ce-cli containerd.io** 
      
    

7、验证Docker是否正确安装:


      
          

          **sudo docker run hello-world** 
      
    

8、安装Docker Compose:


      
          

          **sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose** 
      
    

9、添加执行权限:


      
          

          **sudo chmod +x /usr/local/bin/docker-compose** 
      
    

10、验证Docker Compose是否正确安装:


      
          

          **docker-compose --version** 
      
    

现在,您已经成功在Ubuntu上安装了Docker和Docker Compose。您可以使用这些命令来管理和运行容器化的应用程序。

错误:

*failed to update store for object type libnetwork.endpointCnt: Key not found in store


      
          

          **Restart docker deamon would fix it.  
  
For ubuntu:** 
          

          

          **sudo service docker restart**  
      
    

picture.image四、创建Supabase项目

Supabase 是一个开源的Firebase替代品。使用 Postgres 数据库、身份验证、即时 API、边缘函数、实时订阅、存储和向量嵌入。一个免费账户可以创建2个项目。

4.1、注册账户

picture.image

前往 https://supabase.com/ 可以注册免费账户。

4.2、创建项目

picture.image

3、配置网站URL和重定向地址

picture.image

主要用于密码重置和电子邮件重定向跳转链接。地址为系统前端访问地址:

http://ip:3000

picture.image

picture.image

五、部署Quivr应用

5.1、克隆存储库


      
          

          **git clone https://github.com/StanGirard/Quivr.git && cd Quivr** 
      
    

可以使用 ls -alh 命令查看所有文件(包含隐藏文件)

一般Quivr每周都会在主分支更新新的内容,会存在一定未知的bug,建议选择一个最新的release稳定版本进行部署

picture.image

5.2、复制.XXXXX_env文件

picture.image

新版本后端代码重构了,新的配置文件注意在 backend/core/ 目录下面。


      
        
            

            

            **cp .backend\_env.example backend/core/.env  
cp .frontend\_env.example frontend/.env** 
          
        
      
    

5.3、更新frontend/.env文件

picture.image


      
          

          **NEXT\_PUBLIC\_ENV=local  
NEXT\_PUBLIC\_BACKEND\_URL=http://你的IP:5050/  
NEXT\_PUBLIC\_SUPABASE\_URL=your supabase project url  
NEXT\_PUBLIC\_SUPABASE\_ANON\_KEY=your supabase api key  
NEXT\_PUBLIC\_JUNE\_API\_KEY=your june api key** 
        
      
    

请注意,如果Quivr部署在本机电脑,backend_url直接使用localhost,如果Quivr部署在本地服务器或者云服务器则需要将后端URL修改为你服务器的实际的IP地址。(很多人会忽略这个配置!)

  • 关于NEXT_PUBLIC_JUNE_API_KEY的配置说明:

Quivr 集成了 June Analytics 提供的API接口,在集成了June Analytics 之后,你只需要在系统中配置正确的June API密钥(即June key),然后June网站会自动开始收集和跟踪系统的数据。

一旦数据开始被收集,你可以登录到June Analytics的仪表板,并在其中查看和分析收集到的数据。June仪表板提供了一个用户友好的界面,用于浏览各种报告、图表和指标,以便你了解用户行为、事件触发和其他关键指标。

通过June仪表板,你可以探索不同的分析视图,如用户活动、事件追踪、转化率等。你可以根据时间范围、特定用户或自定义事件来过滤和细化数据,以获取更具体的见解和洞察。

如果是正式上线的站点,可以按需选择接入,默认可以不用考虑设置此参数,如果需要收集和分析网站的数据,可以去注册June账号,申请一个 June Key

picture.image

picture.image

5.4、更新backend/core/.env文件

picture.image


      
          

          **SUPABASE\_URL=your supabase project url  
SUPABASE\_SERVICE\_KEY=your supabase api key  
PG\_DATABASE\_URL=notimplementedyet  
OPENAI\_API\_KEY=your openai api key** 
        
            **ANTHROPIC\_API\_KEY=null** 
        
          **JWT\_SECRET\_KEY=your supabase jwt secret key  
  
AUTHENTICATE=true  
GOOGLE\_APPLICATION\_CREDENTIALS=<change-me>  
GOOGLE\_CLOUD\_PROJECT=<change-me>  
  
# 默认50M  
MAX\_BRAIN\_SIZE=52428800.   
MAX\_REQUESTS\_NUMBER=2000  
MAX\_BRAIN\_PER\_USER=100  
  
# Private LLM Variables  
PRIVATE=False  
MODEL\_PATH=./local\_models/ggml-gpt4all-j-v1.3-groovy.bin  
  
# RESEND  
RESEND\_API\_KEY=your resend api key  
RESEND\_EMAIL\_ADDRESS=your resend email address** 
      
    

请注意, supabase_url 在您的Supabase仪表板下的项目设置-> API中对应的Project URL** supabase_service_key 在您的Supabase仪表板下的项目设置-> API中找到。使用“Project API keys”部分中找到的 anon public 键。您 JWT_SECRET_KEY 可以在 Project Settings -> JWT Settings -> JWT Secret 下的 supabase 设置中找到。( 注意 ANTHROPIC_API_KEY 可以不配置值,但key不能删除,否则构建会失败 )

picture.image

picture.image

5.5、创建Supabase数据库和表

picture.image

通过Web界面( SQL编辑器 ->“ New Query ”)在Supabase数据库上运行以下迁移脚本。

picture.image

picture.image

数据库脚本地址:

https://github.com/StanGirard/quivr/blob/main/scripts/tables.sql


      
        
            

            

            **-- Create users table  
CREATE TABLE IF NOT EXISTS users(  
    user\_id UUID REFERENCES auth.users (id),  
    email TEXT,  
    date TEXT,  
    requests\_count INT,  
    PRIMARY KEY (user\_id, date)  
);  
  
-- Create chats table  
CREATE TABLE IF NOT EXISTS chats(  
    chat\_id UUID DEFAULT uuid\_generate\_v4() PRIMARY KEY,  
    user\_id UUID REFERENCES auth.users (id),  
    creation\_time TIMESTAMP DEFAULT current\_timestamp,  
    history JSONB,  
    chat\_name TEXT  
);  
  
  
-- Create vector extension  
CREATE EXTENSION IF NOT EXISTS vector;  
  
-- Create vectors table  
CREATE TABLE IF NOT EXISTS vectors (  
    id UUID DEFAULT uuid\_generate\_v4() PRIMARY KEY,  
    content TEXT,  
    metadata JSONB,  
    embedding VECTOR(1536)  
);  
  
-- Create function to match vectors  
CREATE OR REPLACE FUNCTION match\_vectors(query\_embedding VECTOR(1536), match\_count INT, p\_brain\_id UUID)  
RETURNS TABLE(  
    id UUID,  
    brain\_id UUID,  
    content TEXT,  
    metadata JSONB,  
    embedding VECTOR(1536),  
    similarity FLOAT  
) LANGUAGE plpgsql AS $$  
#variable\_conflict use\_column  
BEGIN  
    RETURN QUERY  
    SELECT  
        vectors.id,  
        brains\_vectors.brain\_id,  
        vectors.content,  
        vectors.metadata,  
        vectors.embedding,  
        1 - (vectors.embedding <=> query\_embedding) AS similarity  
    FROM  
        vectors  
    INNER JOIN  
        brains\_vectors ON vectors.id = brains\_vectors.vector\_id  
    WHERE brains\_vectors.brain\_id = p\_brain\_id  
    ORDER BY  
        vectors.embedding <=> query\_embedding  
    LIMIT match\_count;  
END;  
$$;  
  
-- Create stats table  
CREATE TABLE IF NOT EXISTS stats (  
    time TIMESTAMP,  
    chat BOOLEAN,  
    embedding BOOLEAN,  
    details TEXT,  
    metadata JSONB,  
    id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY  
);  
  
-- Create summaries table  
CREATE TABLE IF NOT EXISTS summaries (  
    id BIGSERIAL PRIMARY KEY,  
    document\_id UUID REFERENCES vectors(id),  
    content TEXT,  
    metadata JSONB,  
    embedding VECTOR(1536)  
);  
  
-- Create function to match summaries  
CREATE OR REPLACE FUNCTION match\_summaries(query\_embedding VECTOR(1536), match\_count INT, match\_threshold FLOAT)  
RETURNS TABLE(  
    id BIGINT,  
    document\_id UUID,  
    content TEXT,  
    metadata JSONB,  
    embedding VECTOR(1536),  
    similarity FLOAT  
) LANGUAGE plpgsql AS $$  
#variable\_conflict use\_column  
BEGIN  
    RETURN QUERY  
    SELECT  
        id,  
        document\_id,  
        content,  
        metadata,  
        embedding,  
        1 - (summaries.embedding <=> query\_embedding) AS similarity  
    FROM  
        summaries  
    WHERE 1 - (summaries.embedding <=> query\_embedding) > match\_threshold  
    ORDER BY  
        summaries.embedding <=> query\_embedding  
    LIMIT match\_count;  
END;  
$$;  
  
-- Create api\_keys table  
CREATE TABLE IF NOT EXISTS api\_keys(  
    key\_id UUID DEFAULT gen\_random\_uuid() PRIMARY KEY,  
    user\_id UUID REFERENCES auth.users (id),  
    api\_key TEXT UNIQUE,  
    creation\_time TIMESTAMP DEFAULT current\_timestamp,  
    deleted\_time TIMESTAMP,  
    is\_active BOOLEAN DEFAULT true  
);  
  
--- Create prompts table  
CREATE TABLE IF NOT EXISTS prompts (  
    id UUID DEFAULT uuid\_generate\_v4() PRIMARY KEY,  
    title VARCHAR(255),  
    content TEXT,  
    status VARCHAR(255) DEFAULT 'private'  
);  
  
--- Create brains table  
CREATE TABLE IF NOT EXISTS brains (  
  brain\_id UUID DEFAULT gen\_random\_uuid() PRIMARY KEY,  
  name TEXT NOT NULL,  
  status TEXT,  
  description TEXT,  
  model TEXT,  
  max\_tokens INT,  
  temperature FLOAT,  
  openai\_api\_key TEXT,  
  prompt\_id UUID REFERENCES prompts(id)  
);  
  
  
-- Create chat\_history table  
CREATE TABLE IF NOT EXISTS chat\_history (  
    message\_id UUID DEFAULT uuid\_generate\_v4(),  
    chat\_id UUID REFERENCES chats(chat\_id),  
    user\_message TEXT,  
    assistant TEXT,  
    message\_time TIMESTAMP DEFAULT current\_timestamp,  
    PRIMARY KEY (chat\_id, message\_id),  
    prompt\_id UUID REFERENCES prompts(id),  
    brain\_id UUID REFERENCES brains(brain\_id)  
);  
  
-- Create brains X users table  
CREATE TABLE IF NOT EXISTS brains\_users (  
  brain\_id UUID,  
  user\_id UUID,  
  rights VARCHAR(255),  
  default\_brain BOOLEAN DEFAULT false,  
  PRIMARY KEY (brain\_id, user\_id),  
  FOREIGN KEY (user\_id) REFERENCES auth.users (id),  
  FOREIGN KEY (brain\_id) REFERENCES brains (brain\_id)  
);  
  
-- Create brains X vectors table  
CREATE TABLE IF NOT EXISTS brains\_vectors (  
  brain\_id UUID,  
  vector\_id UUID,  
  file\_sha1 TEXT,  
  PRIMARY KEY (brain\_id, vector\_id),  
  FOREIGN KEY (vector\_id) REFERENCES vectors (id),  
  FOREIGN KEY (brain\_id) REFERENCES brains (brain\_id)  
);  
  
-- Create brains X vectors table  
CREATE TABLE IF NOT EXISTS brain\_subscription\_invitations (  
  brain\_id UUID,  
  email VARCHAR(255),  
  rights VARCHAR(255),  
  PRIMARY KEY (brain\_id, email),  
  FOREIGN KEY (brain\_id) REFERENCES brains (brain\_id)  
);  
  
--- Create user\_identity table  
CREATE TABLE IF NOT EXISTS user\_identity (  
  user\_id UUID PRIMARY KEY,  
  openai\_api\_key VARCHAR(255)  
);  
  
  
CREATE OR REPLACE FUNCTION public.get\_user\_email\_by\_user\_id(user\_id uuid)  
RETURNS TABLE (email text)  
SECURITY definer  
AS $$  
BEGIN  
  RETURN QUERY SELECT au.email::text FROM auth.users au WHERE au.id = user\_id;  
END;  
$$ LANGUAGE plpgsql;  
  
  
CREATE OR REPLACE FUNCTION public.get\_user\_id\_by\_user\_email(user\_email text)  
RETURNS TABLE (user\_id uuid)  
SECURITY DEFINER  
AS $$  
BEGIN  
  RETURN QUERY SELECT au.id::uuid FROM auth.users au WHERE au.email = user\_email;  
END;  
$$ LANGUAGE plpgsql;  
  
  
  
CREATE TABLE IF NOT EXISTS migrations (  
  name VARCHAR(255)  PRIMARY KEY,  
  executed\_at TIMESTAMPTZ DEFAULT current\_timestamp  
);  
  
INSERT INTO migrations (name)   
SELECT '20230809154300\_add\_prompt\_id\_brain\_id\_to\_chat\_history\_table'  
WHERE NOT EXISTS (  
    SELECT 1 FROM migrations WHERE name = '20230809154300\_add\_prompt\_id\_brain\_id\_to\_chat\_history\_table'  
);** 
        
      
    

数据库脚本执行完成后,在Table编辑器中可以看到已经创建完成的表。

picture.image

5.6、设置yarn的超时时间

picture.image

在前端容器构建依赖阶段一般会比较慢,部分依赖可能由于网络原因长时间无法完成会导致yarn连接超时,旧版本可以在 /frontend/Dockerfile 文件中修改yarn install部分的脚本,增加网络超时参数,新版本已增加该参数可忽略此步骤。


      
  ***`RUN yarn install --network-timeout 1000000`***  
 


    

picture.image

5.7、构建并启动Quivr

picture.image


      
          

          **docker compose -f docker-compose.yml up --build -d** 
      
    

Quivr构建完成启动后如下图所示:

picture.image

picture.image六、访问Quivr

部署完成后,直接访问 http://ip:3000,第一次部署可以通过邮箱注册账号

picture.image

6.1、添加新大脑

picture.image

Quivr 有一个“大脑”的概念。它们是封闭的信息体,可用于为大型语言模型 (LLM) 提供上下文,以回答有关特定主题的问题。

LLM接受过各种各样的数据培训,但要回答有关特定主题的问题或用于围绕特定主题进行推论,需要向他们提供该主题的上下文。Quivr 使用大脑作为提供上下文的直观方式。

当在 Quivr 中选择大脑时,LLM将仅获得该大脑的上下文。这允许用户为特定主题构建大脑,然后用它们来回答有关该主题的问题。未来 Quivr 将会有与其他用户共享大脑的功能。

在Quivr新版本中,可以支持创新多个知识库大脑,实现知识库的内容检索隔离,同时还支持对支持库进行授权,只允许授权用户才能访问,也可以通过分享链接的方式共享知识库。比几个月前的版本功能更加完善。

picture.image

1)、要使用大脑,只需从 Quivr 界面右上角标题中的“使用大脑”图标中选择菜单即可。

2)、我们可以通过单击“创建大脑”按钮来创建一个新的大脑。系统将提示您输入大脑的名称。你也可以使用账户生成的默认大脑。

3)、要切换到不同的大脑,只需单击菜单中的大脑名称并选择您想要使用的大脑即可。

4)、如果你没有选择大脑,则你上传的任何文档都将添加到默认大脑中。

5)、在新建大脑知识库界面中,可以设置使用的模型和模型相关参数,同时也可以针对每个知识库大脑设置独有的Prompt以及所使用的OpenAI API Key,不设置则默认读取配置文件中配置的Key。

注意:如果在使用聊天功能时,需要从菜单中先选择一个大脑才能使用聊天功能。

6.2、共享知识库

picture.image

在选择大脑界面,我们点击大脑后面的分享按钮,通过URL或者发邮件的方式分享或者邀请其它用户加入大脑,共享知识库。

picture.image

picture.image

Quivr 中通过集成 Resend API,用于通过电子邮件邀请来处理共享大脑。

在 /backend/core/.env文件中引入了两个环境变量来配置发送邮件的功能:

  • RESEND_API_KEY :这是 Resend 为我们的应用程序提供的唯一 API 密钥。它使我们能够以安全的方式与 Resend 平台进行通信。
  • RESEND_EMAIL_ADDRESS :这是我们通过重新发送发送电子邮件时用作发件人地址的电子邮件地址。

从环境变量中获取 Resend API 密钥和电子邮件地址后,我们使用它通过 resend.Emails.send 方法发送电子邮件。

6.2、上传知识库

picture.image

新建完知识库大脑后,就可以选择对应的知识库,上传文档构建向量数据了,支持文档、音频、视频和网页链接,所有文件最终都会抽取文件中的文本内容通过调用大模型的API构建向量数据。

picture.image

文件上传完成后,会有如下提示信息

picture.image

6.3、查询知识库

picture.image

知识库文档构建完成后,就可以对当前选择的知识库大脑进行内容检索了,这里我们以鲁迅先生在日本留学的老师藤野先生为例来测试一下Quivr是否正确识别了知识库文档的内容。

picture.image

在没学习专有知识之前,GPT模型不知道鲁迅先生在日本学医的老师是谁,一般会胡乱给出一个日本人的名字,而且多次询问,人命还不一致。

picture.image

在上传完关于鲁迅先生写的《藤野先生》部分文章内容之后,我们再次询问发现可以成功检索正确的答案了。

picture.image

picture.image七、本地化LLM支持

Quivr 在0.0.46版本可以正式支持接入本地LLM大模型,目前只支持由 GPT4All 提供支持的私有 LLM 模型(其他开源模型即将推出),基本上与 PrivateGPT 项目提供的功能类似。意味着你的数据永远存储在本地。LLM 将下载到服务器并在本地对你的问题运行推理。

7.1、使用方法

1)、在 /backend/core/.env 文件中将“private”属性设置为 True。您还可以在 .env 文件中设置其他模型参数。

2)、GPT4All 模型下载地址:

https://gpt4all.io/models/ggml-gpt4all-j-v1.3-groovy.bin

将下载的 GPT4All 模型放在 /backend/local_models 文件夹中。

GPT4All

是一个开源软件生态系统,允许任何人在 日常硬件 上训练和部署 强大定制的 大型语言模型 (LLM)。 Nomic AI 负责监督对开源生态系统的贡献,确保质量、安全性和可维护性。

GPT4All 软件生态系统与以下 Transformer 架构兼容:

  • Falcon
  • LLaMA (including OpenLLaMA )
  • MPT (including Replit )
  • GPT-J
  • Replit - 基于 Replit Inc. 的 Replit 架构
  • StarCoder - 基于 BigCode 的 StarCoder 架构

具体支持的模型型号列表可以从 GPT4All 的网站上查看详尽列表,或下载任何支持的模型。使用这些架构之一训练的任何模型都可以量化,并使用所有 GPT4All 绑定在本地运行,并在聊天客户端。您可以通过为 gpt4all 后端做出贡献来添加新变体。

7.2、未来计划

picture.image

Quivr 计划在本地私有化 LLM 功能中添加更多模型。使用 Hugging Face 的本地嵌入模型来减少对 OpenAI API 的依赖。未来还将添加在前端和 API 中使用私有 LLM 模型的功能。目前的版本只有部署后端才能使用。

picture.image

八、Quivr路线图

picture.image九、References

  • Quivr GitHub

https://github.com/StanGirard/quivr

  • Quivr FastAPI

https://api.quivr.app/docs

  • Resend API

https://resend.com/overview

  • June Analytics

https://analytics.june.so/

  • GPT4All WebSite

https://gpt4all.io/index.html

  • GPT4All Models

https://gpt4all.io/models/ggml-gpt4all-j-v1.3-groovy.bin

  • GPT4All Supported Models

https://raw.githubusercontent.com/nomic-ai/gpt4all/main/gpt4all-chat/metadata/models.json

picture.image

如果你对这篇文章感兴趣,而且你想要了解更多关于AI 领域的实战技巧,可以

关注「技术狂潮AI」公众号

。在这里,你可以看到最新最热的AIGC 领域的干货文章和案例实战教程。

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
CloudWeGo白皮书:字节跳动云原生微服务架构原理与开源实践
本书总结了字节跳动自2018年以来的微服务架构演进之路
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论