前言
在当今数字时代,聊天机器人已经成为了许多企业和个人的不可或缺的工具。那么,如何构建一个高效、准确的聊天机器人呢?本文将介绍如何使用自定义知识库和OpenAI的GPT模型来构建自己的ChatGPT。
chatGPT对很多问题会有自己「独特」的见解,尤其是对于中文输入而言。比如:
本项目通过基于llamaIndex库,将自己的数据和 LLM 结合,得到更适合自己的模型。比如,我们可以把一本书通过 LlamaIndex 喂给 ChatGPT, 得到的模型里就有了我们最近喂进去的知识,然后我们可以再用自然语言向 ChatGPT 提问,就会得到包含了新知识的答案。据实测结果,它对新知识的理解归纳总结能力都很强。
先来看下最终的效果:
当我们直接向chatGPT提问关于宋代姜夔诗人所写的扬州慢这首诗相关的问题时,会有好多回答的错误:
可以看到有很多错误的地方,比如姜夔是宋代诗人,千岩老人是巫师等错误。
对比下我们的项目。
训练模型内容如下:
使用项目训练后结果如下:
可以看到,内容基本准确,且基于训练模型可以进一步扩展出相关的内容。这让人感到一扇新的大门正在打开。这个方案可能能够替代几乎所有的说明书、客服,甚至一些高阶职位。
项目简介
思路来源于:一篇英文文章How To Build Your Own Custom ChatGPT With Custom Knowledge Base
GPT模型和自然语言处理
GPT(Generative Pre-trained Transformer)模型是OpenAI推出的一种基于Transformer架构的语言模型,它已经成为了自然语言处理领域的重要里程碑之一。GPT模型能够在不同的任务上表现出色,如文本生成、问答系统、语言翻译等。
自定义知识库的作用
为了让聊天机器人能够更加准确地回答用户的问题,我们可以利用自定义知识库来增强聊天机器人的回答能力。自定义知识库是指根据具体应用场景和需求,构建一个包含各种实际知识和信息的数据库。在ChatGPT中,我们可以将自定义知识库集成到GPT模型中,使得机器人可以在回答问题时更加准确和实用。
工作原理
使用LlamaIndex创建文档数据索引。
使用自然语言搜索索引。
LlamaIndex将检索相关部分并传递给GPT提示符。 LlamaIndex会将您的原始文档数据转换为可查询向量化索引。它将利用此索引根据查询和数据匹配程度找到最相关的部分。然后,信息将被加载到提示符中,并发送给GPT,以便GPT具有必要的背景来回答您的问题。
项目环境配置
项目是通过后端python,前端react构建的
获取OpenAI API密钥
在使用OpenAI API之前,需要获取OpenAI API密钥。在OpenAI官方网站获取。
安装Python≥3.7
安装pip3
node>=16
获取google的文档的ID和JSON文件。
具体步骤如下:
-
前往Google Cloud Console网站(console.cloud.google.com)。
-
如果尚未创建新项目,请通过单击顶部导航栏中的“选择项目”下拉菜单并选择“新建项目”来创建。按照提示为您的项目命名并选择要关联的组织。
-
创建完毕后,请从顶部导航栏的下拉菜单中选择该项目。
-
从左侧菜单中进入“API和服务”部分,并点击页面顶部的“+启用API和服务”按钮。
-
在搜索框中搜索“Google Docs API”,然后从结果列表中进行选择。
-
单击“启用”按钮以为您的项目启用API。
-
点击OAuth同意屏幕菜单,创建一个应用程序名称(例如,“mychatbot”),然后输入支持电子邮件,保存并添加范围。
您还必须添加测试用户,因为这个 Google 应用程序尚未获得批准。这可以是您自己的电子邮件。
然后,您需要为您的项目设置凭据以使用API。为此,请从左侧菜单中进入“凭据”部分,然后单击“创建凭据”。选择“OAuth客户端ID”,并按照提示设置您的凭据。
一旦您的凭据设置完成,您可以下载JSON文件并将其存储在您的应用程序根目录中,如下所示:
一旦您设置好了您的凭据,您就可以从您的Python项目访问Google Docs API。前往您的Google Docs,打开其中几个文档,并获取可以在您浏览器URL栏中看到的唯一ID,如下图所示:
后端配置
首先新建我们的项目,我这里取名为chatgpt—llama。然后创建一个文件夹server。
cd server
安装依赖
安装依赖慢的话可以用国内镜像地址
pip install openai==0.27.2
pip install llama-index==0.4.28
pip install google-auth-oauthlib==1.0.0
将刚刚下载google文件的JSON下载文件,导入当前文件夹
新建main.py文件,用以测试
import os
import pickle
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from llama_index import GPTSimpleVectorIndex, download_loader
os.environ['OPENAI_API_KEY'] = 'sk-dp18xgeXtxVOEKi2xxxxxxxxLBWPOPxLO2xRCXnxCF1'
def authorize_gdocs():
google_oauth2_scopes = [
"https://www.googleapis.com/auth/documents.readonly"
]
cred = None
if os.path.exists("token.pickle"):
with open("token.pickle", 'rb') as token:
cred = pickle.load(token)
if not cred or not cred.valid:
if cred and cred.expired and cred.refresh_token:
cred.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file("credentials.json", google_oauth2_scopes)
cred = flow.run_local_server(port=0)
with open("token.pickle", 'wb') as token:
pickle.dump(cred, token)
if __name__ == '__main__':
authorize_gdocs()
GoogleDocsReader = download_loader('GoogleDocsReader')
gdoc_ids = ['1TFBCpKB-veDhpGYxxxxxxxxxSfIMwduBZj_YLj240']
loader = GoogleDocsReader()
documents = loader.load_data(document_ids=gdoc_ids)
index = GPTSimpleVectorIndex(documents)
while True:
prompt = input("Type prompt...")
response = index.query(prompt)
print(response)
# Get the last token usage
last_token_usage = index.llm_predictor.last_token_usage
print(f"last_token_usage={last_token_usage}")
运行后在浏览器中点击确定授权登陆。
授权成功后会自动生成token.json文件和token.pickle文件
此时基本已经成功可以在控制台,进行输入输出:
新建api.py文件, 用以定义接口输入输出
pip install flask
pip install flask_cors
主要定义了http://127.0.0.1:8000/predict接口。用以数据交互。flask_cors包是用以支持跨域的。
import os
import pickle
import requests
from flask import Flask, jsonify, request
from flask_cors import CORS
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from llama_index import GPTSimpleVectorIndex, download_loader
os.environ['OPENAI_API_KEY'] = 'xxxxx'
def authorize_gdocs():
google_oauth2_scopes = [
"https://www.googleapis.com/auth/documents.readonly"
]
cred = None
if os.path.exists("token.pickle"):
with open("token.pickle", 'rb') as token:
cred = pickle.load(token)
if not cred or not cred.valid:
if cred and cred.expired and cred.refresh_token:
cred.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file("credentials.json", google_oauth2_scopes)
cred = flow.run_local_server(port=0)
with open("token.pickle", 'wb') as token:
pickle.dump(cred, token)
app = Flask(__name__)
CORS(app)
@app.route('/predict', methods=['POST'])
def predict():
data = request.json
print(data)
prompt = data['prompt']
response = index.query(prompt)
return jsonify({'response': response})
@app.route('/test')
def test():
return 'Hello World!'
if __name__ == '__main__':
authorize_gdocs()
GoogleDocsReader = download_loader('GoogleDocsReader')
gdoc_ids = ['xxxxx']
loader = GoogleDocsReader()
documents = loader.load_data(document_ids=gdoc_ids)
index = GPTSimpleVectorIndex(documents)
app.run(port=8000)
可以用多种方式测试接口。
前端配置
直接在根目录下:yarn create vite使用vite创建一个react+ts的项目。名字为web
模仿chatGPT,布局使用左右布局
右侧内容区域
定义两个数据源,一个发送数据,一个接受数据。
import { useState } from 'react';
import RightContext from '../RightContext';
import RightTextInput from '../RightTextInput';
import styles from './index.module.less'
function RightChat() {
const [inputMessage, setInputMessage] = useState('');
const [outputMessage, setOutputMessage] = useState('');
return (
<div className={styles.container}>
<RightContext inputMessage={inputMessage} outputMessage={outputMessage}/>
<RightTextInput setInputMessage={setInputMessage} setOutputMessage={setOutputMessage}/>
</div>
)
}
export default RightChat
右侧输入框RightTextInput组件
当点击发送按钮时,向接口http://127.0.0.1:8000/predict发送数据,并清空聊天框。
import { useState } from 'react';
import { Input } from 'antd'
import { SendOutlined } from '@ant-design/icons';
import styles from './index.module.less'
const { TextArea } = Input;
interface IProps {
setInputMessage: (inputMessage: string) => void;
setOutputMessage: (outputMessage: string) => void;
}
function RightTextInput(props: IProps) {
const {setInputMessage, setOutputMessage} = props;
const [msg, setMsg] = useState('')
const handleSend = () => {
let temp = msg;
setInputMessage(JSON.stringify(msg))
setMsg('')
// 将api_url替换为你的API接口地址
const api_url = 'http://127.0.0.1:8000/predict';
var testData = { prompt: temp };
// 发送POST请求
fetch(api_url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(testData)
})
.then(response => response.json())
.then(data => {
// 处理响应数据
setOutputMessage(JSON.stringify(data.response.response))
})
.catch(error => {
console.error(error);
});
}
return (
<div className={styles.containerOut}>
<div className={styles.container}>
<TextArea
placeholder="Send Message..."
autoSize={{ minRows: 1 }}
value={msg}
onChange={(e) => { setMsg(e.target.value) }}
bordered={false}
/>
</div>
<SendOutlined onClick={handleSend} className={styles.sendIcon} />
</div>
)
}
export default RightTextInput
右侧内容聊天RightContext组件
接收到发送数据和接受数据时显示不同的样式。
import { Avatar } from 'antd';
import { useEffect, useState } from 'react';
import classnames from 'classnames';
import styles from './index.module.less'
interface IProps {
inputMessage: string;
outputMessage: string;
}
interface IMessage {
type: 'input' | 'output'
message: string
}
function RightContext(props: IProps) {
const { inputMessage, outputMessage } = props;
let [message, setMessage] = useState<IMessage[]>([]);
useEffect(() => {
if (inputMessage) {
let temp: IMessage[] = [...message, { type: 'input', message: inputMessage }]
setMessage(temp)
}
}, [inputMessage])
useEffect(() => {
if (outputMessage) {
let temp: IMessage[] = [...message, { type: 'output', message: outputMessage }]
setMessage(temp)
}
}, [outputMessage])
console.log(message)
return (
<div className={styles.container}>
<div className={styles.chatContainer}>
{
message.map((item,key) => {
return (
<div
className={classnames(styles.chat, { [styles.chatOutput]: item.type === 'output' })}
key={`message-${key}`}
>
<div className={styles.chatMessage}>
<div className={styles.avatar}>
{item.type === 'input' ? (
<Avatar style={{ backgroundColor: '#f56a00' }}>I</Avatar>
) : (
<Avatar style={{ backgroundColor: '#1890ff' }}>O</Avatar>
)}
</div>
<div className={styles.message}>
{JSON.parse(item.message).split("\n").filter((item: string) => item !== '').map((line: string, i: number) => (
<span key={i}>
{line}
<br />
</span>
))}
</div>
</div>
</div>
)
})
}
</div>
</div>
)
}
export default RightContext
最终效果
后期计划
- 结果文字流式显示
- 支持更换主题色
- 左侧框完善
- 语音输入输出
地址
创作不易,如有帮助,感谢🙏star: github.com/KangXinzhi/…