FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 3.6+ 开发。
关键特性:
官方文档:https://fastapi.tiangolo.com/zh/
准确来说ChatGPT只是OpenAI基于GPT模型开发的一个应用,只不过这个词更流行,更广为人知。对于开发者来说,更准确的说法是GPT模型。目前OpenAI提供的模型包括:
模型 | 描述 |
---|---|
GPT-4 | GPT-3.5的优化版本,理解能力和生成自然语言和代码的能力更好 |
GPT-3.5 | GPT-3的优化版本 |
DALL·E | 文生图AI模型 |
Whisper | 音频转文本AI模型 |
Embeddings | 文本转向量模型 |
Moderation | 敏感词检测模型,用以检查用户的输入是否合规 |
每个模型下面又分别有很多细分的模型,在文本和代码生成场景,官方推荐使用两个模型:gpt-3.5-turbo or gpt-4,本文使用目前成本更优的gpt-3.5-turbo 。相对应的, gpt-4 能理解更复杂的指令,也会尽可能不胡言乱语,但是 gpt-4 成本要高一些,推理速度要慢一些。
GPT模型的应用场景:
OpenAI的更多资源请参考:
https://platform.openai.com/docs
https://github.com/openai/openai-cookbook
本文主要使用了MemFire Cloud的BaaS服务提供的数据库自动生成API以及向量数据库能力。MemFire Cloud的BaaS服务还提供了其他一些方便开发者进行应用开发的功能:
MemFire Cloud更多信息,请参考: https://memfiredb.com/
下面是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?"}
]
)
参数必填项:
AI开发的最主要的工作就是组装合适的messages,以达到更精确的回答用户问题的目的。一条message包含role和content两个元素。其中role包含:
如果你使用过FastAPI,可以跳过本节内容。
如果你有flask或django的基础,强烈建议阅读官方的这两个教程,可以帮助你快速了解fastapi的使用方法。
如果你是web服务的新手,建议从头阅读fastapi的教程文档,写的非常好。
链接: https://fastapi.tiangolo.com/zh/tutorial/first-steps/
我们先以上面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
了解了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服务程序了。
LangChain的文档中对QA场景的AI应用开发有比较具体的讲解,感兴趣的可以深入阅读:
https://python.langchain.com/docs/use_cases/question_answering/
从图中可以看到,大致可以分为如下步骤:
根据上面的流程,我们完全可以自主的实现一个ai应用了,为什么要引入LangChain呢?
如果你通读了LangChain的文档(https://python.langchain.com/docs/use_cases/question_answering/),对于如何借助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()
2.数据切分能力 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)
3.向量化能力 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;
$$;
4.知识检索 上面介绍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帮我们生成最终的答案了。
在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)
我们都知道,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的最佳实践可以参考。
OpenAI的GPT模型本身是没有记忆力的,如果我们希望知识库应用能像ChatGPT一样跟使用者进行连续的对话,需要让我们的应用有记忆能力,并将记忆的信息在下一次对话时发送给openai的模型,以便模型了解前面跟用户聊了些什么。
另外OpenAI的接口是有token限制的,当连续对话的内容超出了一次api调用的token限制时,需要压缩历史对话信息。有两种压缩方式:
不论哪种方式,你都需要对当前会话的历史数据进行记录:
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()
敬请期待我们即将发布的完整实例代码~