为什么挑选这三个组合

  • OpenAI官方SDK是Python,此开发言语首选Python
  • FastAPI是Python言语编写的高性能的现代化Web结构
  • MemFire Cloud供给向量数据库支撑,向量数据库是开发常识库运用的必选项
  • MemFire Cloud供给Supabase保管,LangChain原生支撑Supabase API
  • LangChain是AI运用开发的干流结构,能便利的组合各种AI技能进行运用开发

FastAPI介绍

FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 结构,运用 Python 3.6+ 开发。

要害特性:

  • 快速:可与 NodeJSGo 并肩的极高性能(归功于 Starlette 和 Pydantic)。最快的 Python web 结构之一。
  • 高效编码:进步功用开发速度约 200% 至 300%。*
  • 更少 bug:削减约 40% 的人为(开发者)导致过错。*
  • 智能:极佳的编辑器支撑。处处皆可主动补全,削减调试时刻。
  • 简略:规划的易于运用和学习,阅览文档的时刻更短。
  • 简短:使代码重复最小化。经过不同的参数声明实现丰富功用。bug 更少。
  • 强健:生产可用等级的代码。还有主动生成的交互式文档。
  • 标准化:依据(并完全兼容)API 的相关敞开标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema。

官方文档:fastapi.tiangolo.com/zh/

ChatGPT介绍

准确来说ChatGPT仅仅openai依据GPT模型开发的一个运用,只不过这个词更盛行,更广为人知。关于开发者来说,更准确的说法是GPT模型。现在openai供给的模型包含:

模型 描绘
GPT-4 GPT-3.5的优化版别,了解才能和生成自然言语和代码的才能更好。
GPT-3.5 GPT-3的优化版别。
DALLE 文生图AI模型
Whisper 音频转文本AI模型
Embeddings 文本转向量模型
Moderation 敏感词检测模型,用以查看用户的输入是否合规

每个模型下面又分别有许多细分的模型,在文本和代码生成场景,官方推荐运用两个模型:gpt-3.5-turbo or gpt-4,本文运用现在成本更优的gpt-3.5-turbo 。相对应的, gpt-4 能了解更杂乱的指令,也会尽可能不胡言乱语,可是 gpt-4 成本要高一些,推理速度要慢一些。

GPT模型的运用场景:

  • 编写各种类型的文档,包含学术论文
  • 生成代码
  • 依据常识库进行问题答复
  • 分析文本,包含阅览论文、解读代码等都能担任
  • 对话工具,比方客服
  • 为软件供给自然言语接口
  • 教学导师
  • 翻译
  • 游戏人物AI

openai的更多资源请参阅:

  • platform.openai.com/docs
  • github.com/openai/open…

MemFire Cloud介绍

本文主要运用了MemFire Cloud的BaaS服务供给的数据库主动生成API以及向量数据库才能。MemFire Cloud的BaaS服务还供给了其他一些便利开发者进行运用开发的功用:

  • 完好的用户注册、登录、权限管理API,web前端和App开发人员能够直接调用,几行代码完结用户注册登录功用。
  • 微信小程序、手机短信验证码、github、apple等第三方用户接入支撑。
  • 最盛行的postgres联系数据库支撑,主动生成增删查改API接口,支撑向量化插件pgvector。
  • 供给对象存储接口,便利运用开发者上传下载图片、文档等。
  • 静态保管和自界说域名服务,能够让开发者不需求为部署前端代码专门购买云服务器。
  • 云函数能够便利处理杂乱和敏感的业务逻辑,也能够用来保管完好的api服务。

MemFire Cloud更多信息,请参阅:memfiredb.com/

GPT初体验

下面是openai官方的一个比如:

import os
import openai
openai.organization = "org-kjUiGhsu6S3CI2MUch25dMOF"
openai.api_key = os.getenv("OPENAI_API_KEY")
​
openai.ChatCompletion.create(
 model="gpt-3.5-turbo",
 messages=[
     {"role": "system", "content": "You are a helpful assistant."},
     {"role": "user", "content": "Who won the world series in 2020?"},
     {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
     {"role": "user", "content": "Where was it played?"}
   ]
)

参数必填项:

  • model:模型挑选,这儿用的是gpt-3.5-turbo
  • messages:信息,能够只包含一个问题,也能够包含更多上下文,以协助AI更准确的答复问题。

AI开发的最主要的作业便是拼装适宜的messages,以达到更准确的答复用户问题的意图。一条message包含role和content两个元素。其间role包含:

  • system:相当于系统设定,一般用来告诉ai他要扮演什么人物,怎么答复问题等。
  • assistant:辅助信息,协助AI更好的了解用户的当时问题。一般用于包含多轮对话的上下文信息,也能够用来给出一些问答样例。
  • user:用户的发问内容。

FastAPI编写后端服务

假如你运用过FastAPI,能够越过本节内容。

假如你有flask或django的基础,强烈建议阅览官方的这两个教程,能够协助你快速了解fastapi的运用办法。

  • fastapi.tiangolo.com/zh/tutorial…
  • fastapi.tiangolo.com/zh/tutorial…

假如你是web服务的新手,建议从头阅览fastapi的教程文档,写的非常好。

fastapi.tiangolo.com/zh/tutorial…

咱们先以上面openai的官方示例,来看一下怎么运用fastapi来编写服务端代码,完结与openai的交互,并露出接口给你的web或app客户端运用。为了便利,咱们将一切代码放在一个main.py中演示,假如你要实现一个完好的运用,建议参阅大型运用开发这篇教程,模块化安排你的代码。

接口界说

一个简略的可运转的接口界说:

from fastapi import FastAPI
app = FastAPI()
​
@app.get("/hello")
async def hello():
  return {"message": "你好"}

装置下列依靠就能够运转了:

# 装置依靠
pip install "uvicorn[standard]==0.23.1" "fastapi[all]==0.99.1"# 运转服务
uvicorn main:app --reload

拜访接口:

curl http://127.0.0.1:8000/hello

封装openai调用接口

了解了fastapi的接口界说办法,咱们就能很快将openai的官方示例封装成接口:

from fastapi import FastAPI
import openai
​
app = FastAPI()
​
openai.organization = "org-kjUiGhsu6S3CI2MUch25dMOF"
openai.api_key = os.getenv("OPENAI_API_KEY")
​
@app.get("/openai")
async def openai():
  return openai.ChatCompletion.create(
   model="gpt-3.5-turbo",
   messages=[
       {"role": "system", "content": "You are a helpful assistant."},
       {"role": "user", "content": "Who won the world series in 2020?"},
       {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
       {"role": "user", "content": "Where was it played?"}
     ]
   )

过错处理

在调用openai的接口时,假如产生过错,openai会抛出反常。在FastAPI的服务代码中,假如咱们不处理openai的反常,FastAPI会将反常抛出,并回来客户端500 Internal Server Error。一般咱们需求以更结构化的办法将详细的过错信息回来给接口调用者。

FastAPI供给了反常处理的回调接口,以openai的反常为例,能够经过如下办法注册反常处理函数,以更友好、一致的结构回来过错信息:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from openai import OpenAIError
​
app = FastAPI()
​
# 捕获反常,并回来JSON格式的过错信息
@app.exception_handler(OpenAIError)
async def openai_exception_handler(request: Request, exc: OpenAIError):
  return JSONResponse(
    status_code=exc.http_status,
    content={'code': exc.code, 'message': exc.error},
   )

了解了FastAPI的api界说办法和过错处理办法,咱们根本上能够完结一个简略的web服务程序了。

依据openai开发常识库运用的根本原理

LangChain的文档中对QA场景的AI运用开发有比较详细的解说,感兴趣的能够深入阅览:

python.langchain.com/docs/use_ca…

下图是常识库类AI运用的根本开发流程:

MemFire教程 | FastAPI + MemFire Cloud + LangChain开发ChatGPT应用

从图中能够看到,大致能够分为如下过程:

  1. 文档处理:将pdf、docx、数据库中的数据转换成文本内容。现在openai的chat模型接收的都是文本数据。
  2. 数据切分:将处理过后的文本内容进行切分。切分的意图是便利语义化检索,让检索内容更准确,别的也是为了适配AI接口的约束。gpt-3.5-turbo的最大tokens数量是4k,gpt-3.5-turbo-16k也只要16k,因此文档切分是必需的过程。
  3. 向量化:openai供给了embedding模型,专门用来将文本转换成向量化数据,向量化的意图是便利后续依据用户的输入来检索类似度高的常识。这儿就需求用到向量数据库来存储embedding模型的输出。 以上是常识库处理过程,这一部分一般是运转在运用的后台,需求继续不断的获取最新的常识(比方最新的产品文档、技能手册),并更新向量数据库中的数据。 接下来是用户问答的处理流程:
  4. 常识检索:将用户的问题向量化,然后到向量化数据库中检索类似度最高的常识(能够依据需求选取类似度高的前n项)。
  5. AI辅助答复:将检索到的常识以及设定好的prompt信息一起发送给ai模型,ai模型会结合自己已有的常识以及常识库中检索到的常识进行终究的答案生成。

LangChain根本用法

依据上面的流程,咱们完全能够自主的实现一个ai运用了,为什么要引进LangChain呢?

假如你通读了LangChain的文档,关于怎么借助LangChain完结常识库运用应该有了根本的知道。结合上面的根本原理,咱们来看下LangChain能为咱们供给哪些才能。

  1. 数据加载才能 在上面的过程中,咱们首先要完结的是将已有的常识文档处理成文本数据。LangChain现在已经内置类非常多的文档类型处理才能,包含常见的pdf、docx、markdown、html、json、csv等,同时兼容了一百多种数据源,简直囊括了市面上一切最常用的服务,包含S3、Bilibili、EverNote、Github、Hacker News、Slack、Telegram等等。 下面是加载Web数据的WebBaseLoader的运用办法:
from langchain.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()
  1. 数据切分才能 LangChain供给了文本切分工具,能够便利的将加载后的文本进行切分处理。上面将网页内容加载到data对象之后,能够运用RecursiveCharacterTextSplitter进行文本切分:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size = 500, chunk_overlap = 0)
all_splits = text_splitter.split_documents(data)
  1. 向量化才能

LangChain支撑常见的向量化数据库以及embedding模型接入才能,以MemFire Cloud保管的SupaBase和openai embedding模型为例(参阅):

import os
from supabase.client import Client, create_client
from langchain.vectorstores import SupabaseVectorStore
from langchain.embeddings import OpenAIEmbeddings
​
supabase_url = os.environ.get("SUPABASE_URL")
supabase_key = os.environ.get("SUPABASE_SERVICE_KEY")
client: Client = create_client(supabase_url, supabase_key)
​
vector_store = SupabaseVectorStore.from_documents(
  all_splits, OpenAIEmbeddings(), client=client)

要运用LangChain + pgvector的向量化数据库才能,需求在MemFire Cloud上创立运用,并开启vector扩展,然后创立documents表和函数。能够运用SQL句子完结这些操作:

-- Enable the pgvector extension to work with embedding vectors
create extension vector;
​
-- Create a table to store your documents
create table documents (
   id bigserial primary key,
   content text, -- corresponds to Document.pageContent
   metadata jsonb, -- corresponds to Document.metadata
   embedding vector(1536) -- 1536 works for OpenAI embeddings, change if needed
);
​
CREATE FUNCTION match_documents(query_embedding vector(1536), match_count int)
  RETURNS TABLE(
    id uuid,
    content text,
    metadata jsonb,
    -- we return matched vectors to enable maximal marginal relevance searches
    embedding vector(1536),
    similarity float)
  LANGUAGE plpgsql
  AS $$
  # variable_conflict use_column
BEGIN
  RETURN query
  SELECT
    id,
    content,
    metadata,
    embedding,
    1 -(documents.embedding <=> query_embedding) AS similarity
  FROM
    documents
  ORDER BY
    documents.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;
  1. 常识检索 上面介绍LangChain组合openai embedding 和 pgvector进行向量化处理和存储,LangChain的vectorstore能够直接实现向量化检索功用,将用户的问题转换为最切近的常识库数据:
query = "How to build llm auto agent"
matched_docs = vector_store.similarity_search(query)

matched_docs便是与用户发问相关性最高的的常识库内容。

接下来就能够将matched_docs + 用户的发问打包提交给AI,让AI帮咱们生成终究的答案了。

怎么将常识库和用户问题打包提交给openai

在GPT初体验章节,咱们已经介绍了GPT接口的运用办法和参数含义,这儿咱们能够运用assistant人物将咱们的常识库打包成messages,然后将用户的问题以user人物打包到messages中,最终调用openai的接口:

messages=[
  {"role": "assistant", "content": doc.page_content} for doc in matched_docs
]
messages.append({"role": "user", "content": query})
response = openai.ChatCompletion.create("gpt-3.5-turbo", messages=messages)

你也能够将文档和问题悉数打包成user人物的message,大约的格式:

content = '\n'.join[doc.page_content for doc in matched_docs]
content += f'\n问题:{query}'
messages=[
  {"role": "user", "content": content }
]
response = openai.ChatCompletion.create("gpt-3.5-turbo", messages=messages)

怎么让AI生成更符合预期的结果

咱们都知道,ChatGPT有的时分会胡言乱语,一般会产生在GPT模型中没有相关的常识的时分。为了让AI答复的更严谨,咱们能够给AI一些清晰的指令,比方:

docs = '\n'.join[doc.page_content for doc in matched_docs]
content = f"'''{docs}'''"
content += f'\n问题:{query}'
​
messages = [
   {"role": "system", "content": "我会将文档内容以三引号(''')引起来发送给你,假如你无法从我供给的文档内容中找到答案,请答复:"我无法找到该问题的答案"。请运用中文答复问题。"},
   {"role": "user", "content": content }
]
response = openai.ChatCompletion.create("gpt-3.5-turbo", messages=messages)

这儿有一份GPT的最佳实践能够参阅。

运用MemFire Cloud作为AI的回忆体

openai的GPT模型本身是没有回忆力的,假如咱们期望常识库运用能像ChatGPT一样跟运用者进行接连的对话,需求让咱们的运用有回忆才能,并将回忆的信息鄙人一次对话时发送给openai的模型,以便模型了解前面跟用户聊了些什么。

别的openai的接口是有token约束的,当接连对话的内容超出了一次api调用的token约束时,需求紧缩前史对话信息。有两种紧缩办法:

  1. 办法1:让openai提炼前史对话的概要信息,然后运用概要信息加最新问题进行问答;
  2. 办法2:从前史对话中检索与最新问题相关性比较高的内容,发送给openai;

不论哪种办法,你都需求对当时会话的前史数据进行记载:

  1. 办法1:需求记载不断迭代的摘要信息。由于token数量约束,你不能一次性获得一切前史对话的摘要,因此需求不停的叠加前史摘要和最新对话数据,生成新的摘要并以会话id为标识,记载到数据库中。
  2. 办法2:需求将前史对话的信息经过embedding模型向量化,要并为每个会话构建上下文常识库索引,检索的时分,只检索当时会话的类似内容。

Supabase SDK供给了非常便利的操作数据库的接口,以下为记载会话前史音讯的表以及根本的操作办法:

-- 前史音讯表界说
create table history_messages (
   id bigserial primary key,
   session_id text, -- 会话id
   role text, -- 会话人物(user or ai)
   message text, -- 会话信息
   create_at timestamp default(now()) -- 创立时刻
)

操作前史音讯表的办法:

import os
from supabase.client import Client, create_client
from langchain.vectorstores import SupabaseVectorStore
​
# 初始化客户端
url = os.environ.get("SUPABASE_URL")
key = os.environ.get("SUPABASE_SERVICE_KEY")
client: Client = create_client(url, key)
​
# 往会话xxxxx刺进一条前史音讯
client.table("history_messages").insert({"session_id": "xxxxx", "role": "user", "message": "你好"}).execute()
​
# 查询会话id是xxxxx的一切前史音讯
client.table("history_messages").select("*").eq("session_id", "xxxxx").execute()