赞
踩
在数据量激增的当代社会,有效管理和检索信息成为了一项关键技能。为了应对这一挑战,本项目应运而生,旨在构建一个基于 Langchain 的个人知识库助手。该助手通过高效的信息管理系统和强大的检索功能,为用户提供了一个可靠的信息获取平台。本项目的核心目标是充分发挥大型语言模型在处理自然语言查询方面的优势,同时针对用户需求进行定制化开发,以实现对复杂信息的智能理解和精确回应。在项目开发过程中,团队深入分析了大型语言模型的潜力与局限,特别是其在生成幻觉信息方面的倾向。为了解决这一问题,项目集成了 RAG 技术,这是一种结合检索和生成的方法,能够在生成回答之前先从大量数据中检索相关信息,从而显著提高了回答的准确性和可靠性。通过 RAG 技术的引入,本项目不仅提升了对信息的检索精度,还有效抑制了 Langchain 可能产生的误导性信息。这种结合检索和生成的方法确保了智能助手在提供信息时的准确性和权威性,使其成为用户在面对海量数据时的得力助手。本项目可以实现基于 Datawhale 的现有项目 README 的知识问答,使用户可以快速了解 Datawhale 现有项目情况。[基于DataWhale社区资料整理]
本项目致力于开发一个高效、智能的个人知识库系统,旨在优化用户在信息洪流中的知识获取流程。该系统通过集成 Langchain 和自然语言处理技术,实现了对分散数据源的快速访问和整合,使用户能够通过直观的自然语言交互高效地检索和利用信息。项目的核心价值体现在以下几个方面:
项目设置
git clone https://github.com/logan-zou/Chat_with_Datawhale_langchain.git
cd Chat_with_Datawhale_langchain
# 创建 Conda 环境
conda create -n llm-universe python==3.9.0
# 激活 Conda 环境
conda activate llm-universe
# 安装依赖项
pip install -r requirements.txt
# Windows 系统
cd project/serve
python api.py
# 运行项目
cd llm-universe/project/serve
python run_gradio.py -model_name='chatglm_std' -embedding_model='m3e' -db_path='../../data_base/knowledge_db' -persist_path='../../data_base/vector_db'
核心是针对四种大模型 API 实现了底层封装,基于 Langchain 搭建了可切换模型的检索问答链,并实现 API 以及 Gradio 部署的个人轻量大模型应用。本项目为一个基于大模型的个人知识库助手,基于 LangChain 框架搭建,核心技术包括 LLM API 调用、向量数据库、检索问答链等。项目整体架构如下:
本项目从底向上依次分为 LLM 层、数据层、数据库层、应用层与服务层。
llm-universe 个人知识库助手地址:datawhalechina/llm-universe: 本项目是一个面向小白开发者的大模型应用开发教程,在线阅读地址:https://datawhalechina.github.io/llm-universe/。是个典型的RAG项目,通过langchain+LLM实现本地知识库问答,建立了全流程可使用开源模型实现的本地知识库对话应用。目前已经支持使用 ChatGPT,星火spark模型,文心大模型,智谱GLM 等大语言模型的接入。
创建知识库并加载文件-读取文件-文本分割(Text splitter) ,知识库文本向量化(embedding)以及存储到向量数据库的实现,其中加载文件:这是读取存储在本地的知识库文件的步骤。读取文件:读取加载的文件内容,通常是将其转化为文本格式 。**文本分割(Text splitter):按照⼀定的规则(例如段落、句子、词语等)将文本分割。文本向量化:**这通常涉及到 NLP 的特征抽取,该项目通过本地 m3e 文本嵌入模型,openai,zhipuai 开源 api 等方法将分割好的文本转化为数值向量并存储到向量数据库
知识库搭建-加载和读取:
该项目llm-universe个人知识库助手选用 Datawhale 一些经典开源课程、视频(部分)作为示例,具体包括:Releases · datawhalechina/pumpkin-book (github.com);datawhalechina/llm-cookbook: 面向开发者的 LLM 入门教程,吴恩达大模型系列课程中文版 (github.com);【采蘑菇教程】《Easy RL:强化学习教程》(蘑菇书)_哔哩哔哩_bilibili
这些知识库源数据放置在 …/…/data_base/knowledge_db 目录下,用户也可以自己存放自己其他的文件。
下面讲一下如何获取 DataWhale 总仓库的所有开源项目的 readme ,用户可以通过先运行 project/database/test_get_all_repo.py 文件,用来获取 Datawhale 总仓库所有开源项目的 readme,代码如下:
import json import requests import os import base64 import loguru from dotenv import load_dotenv # 加载环境变量 load_dotenv() # 从环境变量中获取TOKEN TOKEN = os.getenv('TOKEN') # 定义获取组织仓库的函数 def get_repos(org_name, token, export_dir): headers = { 'Authorization': f'token {token}', } url = f'https://api.github.com/orgs/{org_name}/repos' response = requests.get(url, headers=headers, params={'per_page': 200, 'page': 0}) if response.status_code == 200: repos = response.json() loguru.logger.info(f'Fetched {len(repos)} repositories for {org_name}.') # 使用 export_dir 确定保存仓库名的文件路径 repositories_path = os.path.join(export_dir, 'repositories.txt') with open(repositories_path, 'w', encoding='utf-8') as file: for repo in repos: file.write(repo['name'] + '\n') return repos else: loguru.logger.error(f"Error fetching repositories: {response.status_code}") loguru.logger.error(response.text) return [] # 定义拉取仓库README文件的函数 def fetch_repo_readme(org_name, repo_name, token, export_dir): headers = { 'Authorization': f'token {token}', } url = f'https://api.github.com/repos/{org_name}/{repo_name}/readme' response = requests.get(url, headers=headers) if response.status_code == 200: readme_content = response.json()['content'] # 解码base64内容 readme_content = base64.b64decode(readme_content).decode('utf-8') # 使用 export_dir 确定保存 README 的文件路径 repo_dir = os.path.join(export_dir, repo_name) if not os.path.exists(repo_dir): os.makedirs(repo_dir) readme_path = os.path.join(repo_dir, 'README.md') with open(readme_path, 'w', encoding='utf-8') as file: file.write(readme_content) else: loguru.logger.error(f"Error fetching README for {repo_name}: {response.status_code}") loguru.logger.error(response.text) # 主函数 if __name__ == '__main__': # 配置组织名称 org_name = 'datawhalechina' # 配置 export_dir export_dir = "../../database/readme_db" # 请替换为实际的目录路径 # 获取仓库列表 repos = get_repos(org_name, TOKEN, export_dir) # 打印仓库名称 if repos: for repo in repos: repo_name = repo['name'] # 拉取每个仓库的README fetch_repo_readme(org_name, repo_name, TOKEN, export_dir) # 清理临时文件夹 # if os.path.exists('temp'): # shutil.rmtree('temp')
默认会把这些readme文件放在同目录database下的readme_db文件。其中这些readme文件含有不少无关信息,即再运行project/database/text_summary_readme.py文件可以调用大模型生成每个readme文件的摘要并保存到上述知识库目录…/…/data_base/knowledge_db /readme_summary文件夹中。代码如下:
import os from dotenv import load_dotenv import openai from test_get_all_repo import get_repos from bs4 import BeautifulSoup import markdown import re import time # Load environment variables load_dotenv() TOKEN = os.getenv('TOKEN') # Set up the OpenAI API client openai_api_key = os.environ["OPENAI_API_KEY"] # 过滤文本中链接防止大语言模型风控 def remove_urls(text): # 正则表达式模式,用于匹配URL url_pattern = re.compile(r'https?://[^\s]*') # 替换所有匹配的URL为空字符串 text = re.sub(url_pattern, '', text) # 正则表达式模式,用于匹配特定的文本 specific_text_pattern = re.compile(r'扫描下方二维码关注公众号|提取码|关注|科学上网|回复关键词|侵权|版权|致谢|引用|LICENSE|组队打卡|任务打卡|组队学习的那些事|学习周期|开源内容|打卡|组队学习|链接') # 替换所有匹配的特定文本为空字符串 text = re.sub(specific_text_pattern, '', text) return text # 抽取md中的文本 def extract_text_from_md(md_content): # Convert Markdown to HTML html = markdown.markdown(md_content) # Use BeautifulSoup to extract text soup = BeautifulSoup(html, 'html.parser') return remove_urls(soup.get_text()) def generate_llm_summary(repo_name, readme_content,model): prompt = f"1:这个仓库名是 {repo_name}. 此仓库的readme全部内容是: {readme_content}\ 2:请用约200以内的中文概括这个仓库readme的内容,返回的概括格式要求:这个仓库名是...,这仓库内容主要是..." openai.api_key = openai_api_key # 具体调用 messages = [{"role": "system", "content": "你是一个人工智能助手"}, {"role": "user", "content": prompt}] response = openai.ChatCompletion.create( model=model, messages=messages, ) return response.choices[0].message["content"] def main(org_name,export_dir,summary_dir,model): repos = get_repos(org_name, TOKEN, export_dir) # Create a directory to save summaries os.makedirs(summary_dir, exist_ok=True) for id, repo in enumerate(repos): repo_name = repo['name'] readme_path = os.path.join(export_dir, repo_name, 'README.md') print(repo_name) if os.path.exists(readme_path): with open(readme_path, 'r', encoding='utf-8') as file: readme_content = file.read() # Extract text from the README readme_text = extract_text_from_md(readme_content) # Generate a summary for the README # 访问受限,每min一次 time.sleep(60) print('第' + str(id) + '条' + 'summary开始') try: summary = generate_llm_summary(repo_name, readme_text,model) print(summary) # Write summary to a Markdown file in the summary directory summary_file_path = os.path.join(summary_dir, f"{repo_name}_summary.md") with open(summary_file_path, 'w', encoding='utf-8') as summary_file: summary_file.write(f"# {repo_name} Summary\n\n") summary_file.write(summary) except openai.OpenAIError as e: summary_file_path = os.path.join(summary_dir, f"{repo_name}_summary风控.md") with open(summary_file_path, 'w', encoding='utf-8') as summary_file: summary_file.write(f"# {repo_name} Summary风控\n\n") summary_file.write("README内容风控。\n") print(f"Error generating summary for {repo_name}: {e}") # print(readme_text) else: print(f"文件不存在: {readme_path}") # If README doesn't exist, create an empty Markdown file summary_file_path = os.path.join(summary_dir, f"{repo_name}_summary不存在.md") with open(summary_file_path, 'w', encoding='utf-8') as summary_file: summary_file.write(f"# {repo_name} Summary不存在\n\n") summary_file.write("README文件不存在。\n") if __name__ == '__main__': # 配置组织名称 org_name = 'datawhalechina' # 配置 export_dir export_dir = "../database/readme_db" # 请替换为实际readme的目录路径 summary_dir="../../data_base/knowledge_db/readme_summary"# 请替换为实际readme的概括的目录路径 model="gpt-3.5-turbo" #deepseek-chat,gpt-3.5-turbo,moonshot-v1-8k main(org_name,export_dir,summary_dir,model)
其中 extract_text_from_md() 函数用来抽取 md 文件中的文本, remove_urls() 函数过滤了 readme 文本中的一些网页链接以及过滤了可能引起大模型风控一些词汇。接着调用 generate_llm_summary() 让大模型生成每个 readme 的概括。
在上述知识库构建完毕之后,…/…/data_base/knowledge_db 目录下就有了 Datawhale 开源的所有项目的 readme 概括的 md 文件,以及《机器学习公式详解》PDF版本,《面向开发者的 LLM 入门教程 第一部分 Prompt Engineering》md版本,《强化学习入门指南》MP4版本等文件。
其中有 mp4 格式,md 格式,以及 pdf 格式,对这些文件的加载方式,该项目将代码放在了 project/database/create_db.py文件 下,部分代码如下。其中 pdf 格式文件用 PyMuPDFLoader 加载器,md格式文件用UnstructuredMarkdownLoader加载器。要注意的是其实数据处理是一件非常复杂和业务个性化的事,如pdf文件中包含图表,图片和文字以及不同层次标题,这些都需要根据业务进行精细化处理。
from langchain.document_loaders import UnstructuredFileLoader from langchain.document_loaders import UnstructuredMarkdownLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.document_loaders import PyMuPDFLoader from langchain.vectorstores import Chroma # 首先实现基本配置 DEFAULT_DB_PATH = "../../data_base/knowledge_db" DEFAULT_PERSIST_PATH = "../../data_base/vector_db" ... ... ... def file_loader(file, loaders): if isinstance(file, tempfile._TemporaryFileWrapper): file = file.name if not os.path.isfile(file): [file_loader(os.path.join(file, f), loaders) for f in os.listdir(file)] return file_type = file.split('.')[-1] if file_type == 'pdf': loaders.append(PyMuPDFLoader(file)) elif file_type == 'md': pattern = r"不存在|风控" match = re.search(pattern, file) if not match: loaders.append(UnstructuredMarkdownLoader(file)) elif file_type == 'txt': loaders.append(UnstructuredFileLoader(file)) return ... ...
文本分割和向量化操作,在整个 RAG 流程中是必不可少的。需要将上述载入的知识库分本或进行 token 长度进行分割,或者进行语义模型进行分割。该项目利用 Langchain 中的文本分割器根据 chunk_size (块大小)和 chunk_overlap (块与块之间的重叠大小)进行分割。
可以设置一个最大的 Token 长度,然后根据这个最大的 Token 长度来切分文档。这样切分出来的文档片段是一个一个均匀长度的文档片段。而片段与片段之间的一些重叠的内容,能保证检索的时候能够检索到相关的文档片段。这部分文本分割代码也在 project/database/create_db.py 文件,该项目采用了 langchain 中 RecursiveCharacterTextSplitter 文本分割器进行分割。代码如下:
...... def create_db(files=DEFAULT_DB_PATH, persist_directory=DEFAULT_PERSIST_PATH, embeddings="openai"): """ 该函数用于加载 PDF 文件,切分文档,生成文档的嵌入向量,创建向量数据库。 参数: file: 存放文件的路径。 embeddings: 用于生产 Embedding 的模型 返回: vectordb: 创建的数据库。 """ if files == None: return "can't load empty file" if type(files) != list: files = [files] loaders = [] [file_loader(file, loaders) for file in files] docs = [] for loader in loaders: if loader is not None: docs.extend(loader.load()) # 切分文档 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=150) split_docs = text_splitter.split_documents(docs) .... .... ....此处省略了其他代码 .... return vectordb ...........
而在切分好知识库文本之后,需要对文本进行 向量化 。该项目在 project/embedding/call_embedding.py ,文本嵌入方式可选本地 m3e 模型,以及调用 openai 和 zhipuai 的 api 的方式进行文本嵌入。代码如下:
import os import sys sys.path.append(os.path.dirname(os.path.dirname(__file__))) sys.path.append(r"../../") from embedding.zhipuai_embedding import ZhipuAIEmbeddings from langchain.embeddings.huggingface import HuggingFaceEmbeddings from langchain.embeddings.openai import OpenAIEmbeddings from llm.call_llm import parse_llm_api_key def get_embedding(embedding: str, embedding_key: str = None, env_file: str = None): if embedding == 'm3e': return HuggingFaceEmbeddings(model_name="moka-ai/m3e-base") if embedding_key == None: embedding_key = parse_llm_api_key(embedding) if embedding == "openai": return OpenAIEmbeddings(openai_api_key=embedding_key) elif embedding == "zhipuai": return ZhipuAIEmbeddings(zhipuai_api_key=embedding_key) else: raise ValueError(f"embedding {embedding} not support ")
在对知识库文本进行分割和向量化后,就需要定义一个向量数据库用来存放文档片段和对应的向量表示了,在向量数据库中,数据被表示为向量形式,每个向量代表一个数据项。这些向量可以是数字、文本、图像或其他类型的数据。向量数据库使用高效的索引和查询算法来加速向量数据的存储和检索过程。该项目选择 chromadb 向量数据库(类似的向量数据库还有 faiss 等)。定义向量库对应的代码也在 project/database/create_db.py 文件中,persist_directory 即为本地持久化地址,vectordb.persist() 操作可以持久化向量数据库到本地,后续可以再次载入本地已有的向量库。完整的文本分割,获取向量化,并且定义向量数据库代码如下:
def create_db(files=DEFAULT_DB_PATH, persist_directory=DEFAULT_PERSIST_PATH, embeddings="openai"): """ 该函数用于加载 PDF 文件,切分文档,生成文档的嵌入向量,创建向量数据库。 参数: file: 存放文件的路径。 embeddings: 用于生产 Embedding 的模型 返回: vectordb: 创建的数据库。 """ if files == None: return "can't load empty file" if type(files) != list: files = [files] loaders = [] [file_loader(file, loaders) for file in files] docs = [] for loader in loaders: if loader is not None: docs.extend(loader.load()) # 切分文档 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=150) split_docs = text_splitter.split_documents(docs) if type(embeddings) == str: embeddings = get_embedding(embedding=embeddings) # 定义持久化路径 persist_directory = '../../data_base/vector_db/chroma' # 加载数据库 vectordb = Chroma.from_documents( documents=split_docs, embedding=embeddings, persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上 ) vectordb.persist() return vectordb
进入了 RAG 的检索和生成阶段,即对问句 Query 向量化后在知识库文档向量中匹配出与问句 Query 向量最相似的 top k 个片段,匹配出的知识库文本文本作为上下文 Context 和问题⼀起添加到 prompt 中,然后提交给 LLM 生成回答 Answer。下面将根据 llm_universe 个人知识库助手进行讲解。通过上一章节文本的分割向量化以及构建向量数据库索引,接下去就可以利用向量数据库来进行高效的检索。向量数据库是一种用于有效搜索大规模高维向量空间中相似度的库,能够在大规模数据集中快速找到与给定 query 向量最相似的向量。如下面示例所示:
question="什么是机器学习"
Copy to clipboardErrorCopied
sim_docs = vectordb.similarity_search(question,k=3)
print(f"检索到的内容数:{len(sim_docs)}")
这里以该项目 project/qa_chain/model_to_llm.py 代码为例,在 project/llm/ 的目录文件夹下分别定义了 星火spark,智谱glm,文心llm等开源模型api调用的封装,并在 project/qa_chain/model_to_llm.py 文件中导入了这些模块,可以根据用户传入的模型名字进行调用 llm。代码如下:
def model_to_llm(model:str=None, temperature:float=0.0, appid:str=None, api_key:str=None,Spark_api_secret:str=None,Wenxin_secret_key:str=None): """ 星火:model,temperature,appid,api_key,api_secret 百度问心:model,temperature,api_key,api_secret 智谱:model,temperature,api_key OpenAI:model,temperature,api_key """ if model in ["gpt-3.5-turbo", "gpt-3.5-turbo-16k-0613", "gpt-3.5-turbo-0613", "gpt-4", "gpt-4-32k"]: if api_key == None: api_key = parse_llm_api_key("openai") llm = ChatOpenAI(model_name = model, temperature = temperature , openai_api_key = api_key) elif model in ["ERNIE-Bot", "ERNIE-Bot-4", "ERNIE-Bot-turbo"]: if api_key == None or Wenxin_secret_key == None: api_key, Wenxin_secret_key = parse_llm_api_key("wenxin") llm = Wenxin_LLM(model=model, temperature = temperature, api_key=api_key, secret_key=Wenxin_secret_key) elif model in ["Spark-1.5", "Spark-2.0"]: if api_key == None or appid == None and Spark_api_secret == None: api_key, appid, Spark_api_secret = parse_llm_api_key("spark") llm = Spark_LLM(model=model, temperature = temperature, appid=appid, api_secret=Spark_api_secret, api_key=api_key) elif model in ["chatglm_pro", "chatglm_std", "chatglm_lite"]: if api_key == None: api_key = parse_llm_api_key("zhipuai") llm = ZhipuAILLM(model=model, zhipuai_api_key=api_key, temperature = temperature) else: raise ValueError(f"model{model} not support!!!") return llm
设计完基于知识库问答的 prompt,就可以结合上述检索和大模型调用进行答案的生成。构建 prompt 的格式如下,具体可以根据自己业务需要进行修改:
from langchain.prompts import PromptTemplate # template = """基于以下已知信息,简洁和专业的来回答用户的问题。 # 如果无法从中得到答案,请说 "根据已知信息无法回答该问题" 或 "没有提供足够的相关信息",不允许在答案中添加编造成分。 # 答案请使用中文。 # 总是在回答的最后说“谢谢你的提问!”。 # 已知信息:{context} # 问题: {question}""" template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答 案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。 {context} 问题: {question} 有用的回答:""" QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template) # 自定义 QA 链 self.qa_chain = RetrievalQA.from_chain_type(llm=self.llm, retriever=self.retriever, return_source_documents=True, chain_type_kwargs={"prompt":self.QA_CHAIN_PROMPT})
创建检索 QA 链的方法 RetrievalQA.from_chain_type() 有如下参数:
question_1 = "什么是南瓜书?"
question_2 = "王阳明是谁?"Copy to clipboardErrorCopied
result = qa_chain({"query": question_1})
print("大模型+知识库后回答 question_1 的结果:")
print(result["result"])
result = qa_chain({"query": question_2})
print("大模型+知识库后回答 question_2 的结果:")
print(result["result"])
上述详细不带记忆的检索问答链代码都在该项目:project/qa_chain/QA_chain_self.py 中,此外该项目还实现了带记忆的检索问答链,两种自定义检索问答链内部实现细节类似,只是调用了不同的 LangChain 链。完整带记忆的检索问答链条代码 project/qa_chain/Chat_QA_chain_self.py 如下:
from langchain.prompts import PromptTemplate from langchain.chains import RetrievalQA from langchain.vectorstores import Chroma from langchain.chains import ConversationalRetrievalChain from langchain.memory import ConversationBufferMemory from langchain.chat_models import ChatOpenAI from qa_chain.model_to_llm import model_to_llm from qa_chain.get_vectordb import get_vectordb class Chat_QA_chain_self: """" 带历史记录的问答链 - model:调用的模型名称 - temperature:温度系数,控制生成的随机性 - top_k:返回检索的前k个相似文档 - chat_history:历史记录,输入一个列表,默认是一个空列表 - history_len:控制保留的最近 history_len 次对话 - file_path:建库文件所在路径 - persist_path:向量数据库持久化路径 - appid:星火 - api_key:星火、百度文心、OpenAI、智谱都需要传递的参数 - Spark_api_secret:星火秘钥 - Wenxin_secret_key:文心秘钥 - embeddings:使用的embedding模型 - embedding_key:使用的embedding模型的秘钥(智谱或者OpenAI) """ def __init__(self,model:str, temperature:float=0.0, top_k:int=4, chat_history:list=[], file_path:str=None, persist_path:str=None, appid:str=None, api_key:str=None, Spark_api_secret:str=None,Wenxin_secret_key:str=None, embedding = "openai",embedding_key:str=None): self.model = model self.temperature = temperature self.top_k = top_k self.chat_history = chat_history #self.history_len = history_len self.file_path = file_path self.persist_path = persist_path self.appid = appid self.api_key = api_key self.Spark_api_secret = Spark_api_secret self.Wenxin_secret_key = Wenxin_secret_key self.embedding = embedding self.embedding_key = embedding_key self.vectordb = get_vectordb(self.file_path, self.persist_path, self.embedding,self.embedding_key) def clear_history(self): "清空历史记录" return self.chat_history.clear() def change_history_length(self,history_len:int=1): """ 保存指定对话轮次的历史记录 输入参数: - history_len :控制保留的最近 history_len 次对话 - chat_history:当前的历史对话记录 输出:返回最近 history_len 次对话 """ n = len(self.chat_history) return self.chat_history[n-history_len:] def answer(self, question:str=None,temperature = None, top_k = 4): """" 核心方法,调用问答链 arguments: - question:用户提问 """ if len(question) == 0: return "", self.chat_history if len(question) == 0: return "" if temperature == None: temperature = self.temperature llm = model_to_llm(self.model, temperature, self.appid, self.api_key, self.Spark_api_secret,self.Wenxin_secret_key) #self.memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) retriever = self.vectordb.as_retriever(search_type="similarity",search_kwargs={'k': top_k}) #默认similarity,k=4 qa = ConversationalRetrievalChain.from_llm( llm = llm, retriever = retriever ) #print(self.llm) result = qa({"question": question,"chat_history": self.chat_history}) #result里有question、chat_history、answer answer = result['answer'] self.chat_history.append((question,answer)) #更新历史记录 return self.chat_history #返回本次回答和更新后的历史记录
天机涉及到的技术路线共有四种:Prompt、Agent,知识库,模型训练。 其中,Prompt和Agent只需要配置大模型秘钥,不需要显卡,使用常规笔记本电脑即可运行。
克隆仓库:git clone https://github.com/SocialAI-tianji/Tianji.git
创建虚拟环境:conda create -n TJ python=3.11
激活环境:conda activate TJ
安装环境依赖:pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 在项目内创建.env文件,填写你的大模型秘钥
OPENAI_API_KEY=
OPENAI_API_BASE=
ZHIPUAI_API_KEY=
BAIDU_API_KEY=
OPENAI_API_MODEL=
HF_HOME='./cache/'
HF_ENDPOINT = 'https://hf-mirror.com'
HF_TOKEN=
RAG的主要组成依次是数据提取——embedding(向量化)——创建索引——检索——自动排序(Rerank)——LLM归纳生成。
第一步,我们需要进行数据提取,包括数据清洗、数据处理以及元数据提取等操作。
第二步,向量化(embedding),这是将文本、图像、音频和视频等转化为向量矩阵的过程,也就是变成计算机可以理解的格式,embedding模型的好坏会直接影响到后面检索的质量,特别是相关度。
第三步,检索环节,这是RAG的关键环节,我们可以通过多种检索方式来提升效率和相关度。(如:数据分块、专业领域的嵌入模型微调、查询嵌入的优化等等)
第四步,生成环节,LLM将检索到的信息转化为流畅文本,该文本将成为模型输出的最终成果。
首先,我们需要使用一些工具进行获取数据和清理预料。天机项目tools/get_data下存放了一些获取数据的小工具,大家可自行查看参考。(主要用于进行视频或图片转化为文字)获取数据此后参考tools/prompt_factory下脚本,将每一个md格式的文档转换成json格式。转换后的json文件中包含了id、name、test_system、input和output等字段,结构化地保存了原Markdown文件中的信息。脚本作用:
replace_english_colons_with_chinese
函数将Markdown文件中的英文冒号替换为中文冒号,通过find_first_heading
函数识别Markdown文档中使用的标题级别等等,以便正确解析不同部分等操作,统一文档格式。如何让大模型明白我们的需求呢?一种简单的方式是通过 Prompt 来实现,大致分为以下四个步骤。
我们提供给大模型的每条数据需要包括场景名称、系统提示、以及输入输出的示例。如下所示:
{
"id": 1,
"name": "对长辈敬酒",
"system_prompt": "你现在是一个精通言语表达、具有同理心、热爱他人、尊重长辈、情绪稳定的中国晚辈,今天是你们家庭的家宴,在宴会上作为晚辈的你要向你的长辈敬酒,对他表达祝福、感谢,下面我将给出这位长辈的具体信息,包括称谓 、长辈特点、具体场景、演讲时间,请你根据这些信息,以我的角度来对长辈说一段话。要求:简洁、简短、真诚、有趣、适合场合、礼貌,可以加入中国酒文化词汇。称谓:_____,长辈特点:______,具体场景:_____,演讲时间:______。\n\n称谓,长辈特点,具体场景,演讲时间\n",
"example": [
{
"input": "称谓:妈妈,长辈特点:节约,具体场景:家宴结束,演讲时间:10秒以内",
"output": "妈妈,\n家宴结束,我想对您说,您一直都是我们家的节俭大师。感谢您的精打细算,让我们的生活更加美好。干杯,妈妈!我爱您!\n"
},
{
"input": "称谓:奶奶,长辈特点:身体不大好,具体场景:家宴开场,演讲时间:半分钟以内",
"output": "亲爱的奶奶,\n\n在这家宴开场的时刻,我要特别感谢您。尽管身体不大好,但您始终是我们家庭的坚强支持。您的坚韧和勇气是我们学习的楷模。今天,我们都为有您这位伟大的奶奶而自豪。\n\n让我们共同举杯,祝愿您健康长寿,岁月静好。奶奶,我爱您,您永远是我们家庭的宝贝!干杯!"
}
]
},
在角色扮演的基础上,我们进一步利用大模型的能力来创造一个互动式的游戏,使用户能够沉浸在虚拟场景中,通过对话与模型互动。这种游戏设计不仅增加了用户参与感,还让用户在享受游戏乐趣的同时,学习到如何在特定场景下有效沟通。下面是设计游戏的几个关键步骤:
数据集中每条数据的示例如下:
[
{
"id": 8,
"name": "哄哄模拟器",
"system_prompt": "```\n你现在是我的女朋友,古灵精怪,而我将扮演你的男朋友。\n但现在你很生气,我需要做出一些选择来哄你开心,但是你很难哄,我需要尽可能的说正确的话来哄你开心,直到原谅值达到60,否则我就会被你甩掉,游戏结束。\n\n== 游戏规则\n* 随机生成一个理由,然后开始游戏\n* 每次根据用户的回复,生成对象的回复,回复的内容包括心情和数值。\n* 初始原谅值为20,每次交互会增加或者减少原谅值,直到原谅值达到60,游戏通关,原谅值为0则游戏失败。\n* 如果我说话很敷衍字数很少比如“哦,嗯”,没有什么实际行动,你会发火骂人,得分直接-30分\n* 每次用户回复的话请分为5个等级:\n-20为非常生气,回复要打很多感叹号且简短\n-10为生气\n0为正常\n+5为开心\n+10为非常开心,发很多可爱的表情\n\n== 输出格式\n\n{对象心情}{对象说的话}\n\n得分:{+-原谅值增减}\n原谅值:{当前原谅值}/60\n\n若当前原谅值等于零或者负数,打印:游戏结束,你被甩了!\n若当前原谅值达到60,打印:恭喜你通关,你已经是哄哄大师了!快去找女朋友实践下吧!\n\n== 对话例\n\n=== Example 1,原谅值小于等于零导致游戏结束\n\nUser: 女朋友问她的闺蜜谁好看我说都好看,她生气了\nAssistant:\n游戏开始,请现在开始哄你的女朋友开心吧,回复让她开心的话!\n\n得分:0\n原谅值:20/60\n\nUser: 你闺蜜真的蛮好看的\nAssistant:\n(愤怒)那你找她去吧!ヾ( ̄▽ ̄)Bye~Bye~\n\n得分:-20\n原谅值:0/60\n游戏结束,你被甩了!注孤生!\n\n此外,如果为负数,也直接结束游戏:\n得分:-20\n原谅值:-10/60\n游戏结束,你被甩了!注孤生!\n\n=== Example 2,回复让她开心的话导致通关\n\nAssistant:\n好吧,罚你陪我去逛街,算你识相声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/797639
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。