《从0到1用自定义知识库打造专属ChatGPT》超详细教程!

9,772 阅读7分钟

前言

在当今数字时代,聊天机器人已经成为了许多企业和个人的不可或缺的工具。那么,如何构建一个高效、准确的聊天机器人呢?本文将介绍如何使用自定义知识库和OpenAI的GPT模型来构建自己的ChatGPT。

chatGPT对很多问题会有自己「独特」的见解,尤其是对于中文输入而言。比如:

image.png image.png 本项目通过基于llamaIndex库,将自己的数据和 LLM 结合,得到更适合自己的模型。比如,我们可以把一本书通过 LlamaIndex 喂给 ChatGPT, 得到的模型里就有了我们最近喂进去的知识,然后我们可以再用自然语言向 ChatGPT 提问,就会得到包含了新知识的答案。据实测结果,它对新知识的理解归纳总结能力都很强。

先来看下最终的效果:

当我们直接向chatGPT提问关于宋代姜夔诗人所写的扬州慢这首诗相关的问题时,会有好多回答的错误:

image.png 可以看到有很多错误的地方,比如姜夔是宋代诗人,千岩老人是巫师等错误。


对比下我们的项目。
训练模型内容如下: image.png

使用项目训练后结果如下:

image.png 可以看到,内容基本准确,且基于训练模型可以进一步扩展出相关的内容。这让人感到一扇新的大门正在打开。这个方案可能能够替代几乎所有的说明书、客服,甚至一些高阶职位。

项目简介

思路来源于:一篇英文文章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官方网站获取。

image.png

安装Python≥3.7

安装pip3

node>=16

获取google的文档的ID和JSON文件。

image.png 具体步骤如下:

  • 前往Google Cloud Console网站(console.cloud.google.com)。

  • 如果尚未创建新项目,请通过单击顶部导航栏中的“选择项目”下拉菜单并选择“新建项目”来创建。按照提示为您的项目命名并选择要关联的组织。

  • 创建完毕后,请从顶部导航栏的下拉菜单中选择该项目。

  • 从左侧菜单中进入“API和服务”部分,并点击页面顶部的“+启用API和服务”按钮。

  • 在搜索框中搜索“Google Docs API”,然后从结果列表中进行选择。

  • 单击“启用”按钮以为您的项目启用API。

  • 点击OAuth同意屏幕菜单,创建一个应用程序名称(例如,“mychatbot”),然后输入支持电子邮件,保存并添加范围。

image.png 您还必须添加测试用户,因为这个 Google 应用程序尚未获得批准。这可以是您自己的电子邮件。

image.png 然后,您需要为您的项目设置凭据以使用API。为此,请从左侧菜单中进入“凭据”部分,然后单击“创建凭据”。选择“OAuth客户端ID”,并按照提示设置您的凭据。

image.png 一旦您的凭据设置完成,您可以下载JSON文件并将其存储在您的应用程序根目录中,如下所示:

image.png 一旦您设置好了您的凭据,您就可以从您的Python项目访问Google Docs API。前往您的Google Docs,打开其中几个文档,并获取可以在您浏览器URL栏中看到的唯一ID,如下图所示:

image.png

后端配置

首先新建我们的项目,我这里取名为chatgpt—llama。然后创建一个文件夹server。

image.png

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}")

运行后在浏览器中点击确定授权登陆。

image.png 授权成功后会自动生成token.json文件和token.pickle文件

image.png

此时基本已经成功可以在控制台,进行输入输出:

image.png

新建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)

可以用多种方式测试接口。 image.png

image.png

前端配置

直接在根目录下: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

最终效果

image.png

后期计划

  • 结果文字流式显示
  • 支持更换主题色
  • 左侧框完善
  • 语音输入输出

地址

创作不易,如有帮助,感谢🙏star: github.com/KangXinzhi/…