標題有點長,但是基本也說明出了這篇文章的主旨,那就是利用GPT AI智能回答自己設置好的問題
既能實現自己的AI知識庫機器人,又能節省ChatGPT調用的token成本費用。
代碼倉庫地址
document.ai: 基於GPT3.5的通用本地知識庫解決方案
下面圖片是整個流程:
導入知識庫數據
利用openai的向量接口生成向量數據,然後導入到向量數據庫qdrant
這段代碼會將指定目錄下的所有文件讀取出來,然後將文件中的文本內容進行分割,分割後的結果會被傳入到
to_embeddings函數中,該函數會使用 OpenAI 的 API 將文本內容轉換爲向量。最後,將向量和文件名、文件內容一起作爲一個文檔插入到 Qdrant 數據庫中。
具體來說,這段代碼會遍歷 ./source_data目錄下的所有文件,對於每個文件,它會讀取文件內容,然後將文件內容按照 #####進行分割
分割後的結果會被傳入到 to_embeddings函數中。
to_embeddings函數會使用 OpenAI 的 API 將文本內容轉換爲向量,最後返回一個包含文件名、文件內容和向量的列表。
接下來,將向量和文件名、文件內容一起作爲一個文檔插入到 Qdrant 數據庫中。
其中,count變量用於記錄插入的文檔數量,client.upsert函數用於將文檔插入到 Qdrant 數據庫中。
需要在目錄裏創建.env文件,裏面放OPENAI_API_KEY
OPENAI_API_KEY=sk-Zxxxxxxxxddddddddd
from qdrant_client import QdrantClient from qdrant_client.http.models import Distance, VectorParams from qdrant_client.http.models import PointStruct from dotenv import load_dotenv import os import tqdm import openai def to_embeddings(items): sentence_embeddings = openai.Embedding.create( model="text-embedding-ada-002", input=items[1] ) return [items[0], items[1], sentence_embeddings["data"][0]["embedding"]] if __name__ == '__main__': client = QdrantClient("127.0.0.1", port=6333) collection_name = "data_collection" load_dotenv() openai.api_key = os.getenv("OPENAI_API_KEY") # 創建collection client.recreate_collection( collection_name=collection_name, vectors_config=VectorParams(size=1536, distance=Distance.COSINE), ) count = 0 for root, dirs, files in os.walk("./source_data"): for file in tqdm.tqdm(files): file_path = os.path.join(root, file) with open(file_path, 'r', encoding='utf-8') as f: text = f.read() parts = text.split('#####') item = to_embeddings(parts) client.upsert( collection_name=collection_name, wait=True, points=[ PointStruct(id=count, vector=item[2], payload={"title": item[0], "text": item[1]}), ], ) count += 1
查詢知識庫數據
這是一個基於flask的web應用,主要功能是根據用戶輸入的問題,從Qdrant中搜索相關的文本,然後使用openai的ChatCompletion API進行對話生成,最後將生成的回答返回給用戶。
from flask import Flask from flask import render_template from flask import request from dotenv import load_dotenv from qdrant_client import QdrantClient import openai import os app = Flask(__name__) def prompt(question, answers): """ 生成對話的示例提示語句,格式如下: demo_q: 使用以下段落來回答問題,如果段落內容不相關就返回未查到相關信息:"成人頭疼,流鼻涕是感冒還是過敏?" 1. 普通感冒:您會出現喉嚨發癢或喉嚨痛,流鼻涕,流清澈的稀鼻涕(液體),有時輕度發熱。 2. 常年過敏:症狀包括鼻塞或流鼻涕,鼻、口或喉嚨發癢,眼睛流淚、發紅、發癢、腫脹,打噴嚏。 demo_a: 成人出現頭痛和流鼻涕的症狀,可能是由於普通感冒或常年過敏引起的。如果病人出現咽喉痛和咳嗽,感冒的可能性比較大;而如果出現口、喉嚨發癢、眼睛腫脹等症狀,常年過敏的可能性比較大。 system: 你是一個醫院問診機器人 """ demo_q = '使用以下段落來回答問題:"成人頭疼,流鼻涕是感冒還是過敏?"\n1. 普通感冒:您會出現喉嚨發癢或喉嚨痛,流鼻涕,流清澈的稀鼻涕(液體),有時輕度發熱。\n2. 常年過敏:症狀包括鼻塞或流鼻涕,鼻、口或喉嚨發癢,眼睛流淚、發紅、發癢、腫脹,打噴嚏。' demo_a = '成人出現頭痛和流鼻涕的症狀,可能是由於普通感冒或常年過敏引起的。如果病人出現咽喉痛和咳嗽,感冒的可能性比較大;而如果出現口、喉嚨發癢、眼睛腫脹等症狀,常年過敏的可能性比較大。' system = '你是一個醫院問診機器人' q = '使用以下段落來回答問題,如果段落內容不相關就返回未查到相關信息:"' q += question + '"' # 帶有索引的格式 for index, answer in enumerate(answers): q += str(index + 1) + '. ' + str(answer['title']) + ': ' + str(answer['text']) + '\n' """ system:代表的是你要讓GPT生成內容的方向,在這個案例中我要讓GPT生成的內容是醫院問診機器人的回答,所以我把system設置爲醫院問診機器人 前面的user和assistant是我自己定義的,代表的是用戶和醫院問診機器人的示例對話,主要規範輸入和輸出格式 下面的user代表的是實際的提問 """ res = [ {'role': 'system', 'content': system}, {'role': 'user', 'content': demo_q}, {'role': 'assistant', 'content': demo_a}, {'role': 'user', 'content': q}, ] return res def query(text): """ 執行邏輯: 首先使用openai的Embedding API將輸入的文本轉換爲向量 然後使用Qdrant的search API進行搜索,搜索結果中包含了向量和payload payload中包含了title和text,title是疾病的標題,text是摘要 最後使用openai的ChatCompletion API進行對話生成 """ client = QdrantClient("127.0.0.1", port=6333) collection_name = "data_collection" load_dotenv() openai.api_key = os.getenv("OPENAI_API_KEY") sentence_embeddings = openai.Embedding.create( model="text-embedding-ada-002", input=text ) """ 因爲提示詞的長度有限,所以我只取了搜索結果的前三個,如果想要更多的搜索結果,可以把limit設置爲更大的值 """ search_result = client.search( collection_name=collection_name, query_vector=sentence_embeddings["data"][0]["embedding"], limit=3, search_params={"exact": False, "hnsw_ef": 128} ) answers = [] tags = [] """ 因爲提示詞的長度有限,每個匹配的相關摘要我在這裏只取了前300個字符,如果想要更多的相關摘要,可以把這裏的300改爲更大的值 """ for result in search_result: if len(result.payload["text"]) > 300: summary = result.payload["text"][:300] else: summary = result.payload["text"] answers.append({"title": result.payload["title"], "text": summary}) completion = openai.ChatCompletion.create( temperature=0.7, model="gpt-3.5-turbo", messages=prompt(text, answers), ) return { "answer": completion.choices[0].message.content, "tags": tags, } @app.route('/') def hello_world(): return render_template('index.html') @app.route('/search', methods=['POST']) def search(): data = request.get_json() search = data['search'] res = query(search) return { "code": 200, "data": { "search": search, "answer": res["answer"], "tags": res["tags"], }, } if __name__ == '__main__': app.run(host='0.0.0.0', port=3000)