项目名称:每日花钱助手——RAG-Agent智能对话机器人
报告日期:2024年8月18日
项目负责人:吃不饱的null
项目概述:
本项目旨在开发一个基于RAG(Retrieval-Augmented Generation)的智能对话机器人,能够进行文字形式的聊天,并拓展了Agent智能体。通过了解用户的每日花钱记录,计算支出总额,理解支出的类型与特点,帮助用户更好的管理财务。
本项目使用langchain作为处理项目逻辑的核心库,langchain是一个用于拓展LLM能力,实现Agent等功能的库。
通常情况下,LLM的知识是静态的,停留在模型训练时的知识。因此,当LLM接受到一个问题,它只能回答它已知的部分,遇到现有知识无法回答的问题,LLM会编造错误的回答或是重复问题,这一现象称为“大模型幻觉”。
为了解决这一问题,我们引入了RAG(Retrieval-Augmented Generation)模型。RAG模型结合了检索和生成的优势,通过特定领域的向量数据库,能够在对话过程中提供更加准确的回答。同时,鉴于微调模型需要耗费大量计算资源,使用RAG是更经济效率的选择。
技术方案与实施步骤
模型选择:
本项目使用NVIDIA NIM平台上的microsoft/phi-3-small-128k-instruct模型,用于处理文本数据,分析情感,执行外部tools,为用户提供每日贴心tips。
数据的构建:
针对特定领域进行数据构建,包括对话语料、知识库等。通过向量化处理,将文本数据转换为向量形式,方便模型处理。
向量化处理就是通过embedding模型将各种文本或多模态的数据标注为词向量,存入到向量数据库中。LLM在接收到问题时,会先通过检索模块在向量数据库中找到相似的知识,据此生成回答。
通过向量化处理文本或多模态的知识,可以有效避免LLM的“大模型幻觉”,提高对话的准确性,在特定领域的应用中具有广泛的前景。
功能整合:
在RAG的基础上,通过langchain的agent的模块,可以让LLM从局限的文本对话中拓展到多功能的Agent智能体,能够执行外部任务,获取外部信息。
比如,LLM并不知道现在的时间,知识库实际上也无法解决这个问题。但是,通过拓展tools,LLM可以调用时间API,获取当前时间,并返回给用户。
创建一个Agent,能够把用户的每日花钱记录,计算支出总额,合成为一个合适的sqlite表,作为tools使用,定义一个代码解释器,让LLM执行生成的代码,从数据库中获取数据,计算支出特点,最终实现完整的主动性Agent chain。
实施步骤:
在实现以上基础函数后,就可以搭建一个基于RAG的智能对话机器人了。下面是一个实现:
环境搭建:
创建一个新的virtualenv环境,安装必要的库:
python -m venv env
source env/bin/activate
pip install langchain, faiss-cpu, langchain_community, langchain_nvidia_ai_endpoints,openai
这里使用NVIDIANIM平台的模型,需要在NIM平台上注册并获取API key。
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings,ChatNVDIA
embedder = NVIDIAEmbeddings(model="NV-Embed-QA")
llm = ChatNVIDIA(model="microsoft/phi-3-small-128k-instruct", nvidia_api_key=<YOUR_API_KEY>, max_tokens=1024)
导入必要的模块:
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import CharacterTextSplitter
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains.retrieval import create_retrieval_chain
from langchain_core.prompts import ChatPromptTemplate
代码实现:
实现RAG的核心代码如下:
在本项目中,使用faiss这个轻量化的向量数据库构造retriver模块,实现检索功能。代码简单演示如下:
在这里导入embediings模型,用于向量化数据,使用text_loader将文本文件转换为可接受的输入格式。
使用CharacterTextSplitter将文档切分为合适的大小,其中chunk_size是切分文档的大小,根据文档大小调整,不同的文档大小会影响检索的效率。
embedder = NVIDIAEmbeddings(model="NV-Embed-QA")
def text_loader(url):
# 文本文档加载器
loader = TextLoader(url)
docs = loader.load()
return docs
def create_vector_db(data_path, db_path, loader):
text_splitter = CharacterTextSplitter(chunk_size=2048)
data = loader(data_path)
docs = text_splitter.split_documents(data)
db = FAISS.from_documents(docs, embeddings)
db.save_local(db_path) # 保存路径
create_vector_db("data.txt", "db", text_loader)
使用ChatPromptTemplate生成问题模板,根据context和input生成问题。context即为知识库检索后的文本内容,用于提交与用户问题相近的知识。
需要注意的是FAISS.load_local()函数,需要设置allow_dangerous_deserialization=True,以允许从本地加载向量数据库。
通过langchain包装完全的create_stuff_documents_chain和create_retrieval_chain,实现检索功能。
def rag(msg):
prompt = ChatPromptTemplate.from_template(
"""
以下问题基于提供的 context,分析问题并给出合理的建议回答:
<context>
{context}
</context>
Question: {input}
"""
)
new_db = FAISS.load_local("db", embedder, allow_dangerous_deserialization=True)
document_chain = create_stuff_documents_chain(llm, prompt)
retriever = new_db.as_retriever() # 从向量数据库中检索
retrieval_chain = create_retrieval_chain(retriever, document_chain)
gpt_response = retrieval_chain.invoke({"input": msg})
response = gpt_response['answer']
return response
print(rag("你好"))
到现在就实现了一个简单的RAG过程,LLM根据知识库的知识,给出了一个回答。但是,为了实现Agent的功能,还需要进一步拓展。
LLM需要执行外部任务,获取外部信息,这就需要使用tools模块。定义合适可用的tools,让LLM执行生成的代码,保存到数据库等等。
把实现RAG的retriever包装成一个tool,这样LLM可以基于已有的知识库来进行之后的外部tools执行,强化LLM的主动性。
db_path = "db"
new_db = FAISS.load_local(db_path, embeddings, allow_dangerous_deserialization=True)
retriever = new_db.as_retriever() # 从向量数据库中检索
retriever_tool = create_retriever_tool(
retriever,
"知识库",
"这里存储着有关于问题的背景资料,you must use this tool!",
)
在这里,我们定义一个代码解释器,让LLM执行生成的代码,使用langchain提供的tool装饰器。
from langchain.agents import initialize_agent
from langchain.memory import ConversationBufferWindowMemory
from langchain.tools import tool
from langchain.tools.retriever import create_retriever_tool
def execute_and_return(x):
code = extract_python_code(x.content)[0]
try:
result = exec(str(code))
return "exec result: " + result
except ExceptionType:
return "The code is not executable, don't give up, try again!"
@tool
def interpreter(code: str) -> str:
"""
此函数解释并执行提供的代码,严格去除首行出现的缩进并保证代码符合Python语法。
"""
return execute_and_return(code)
@tool
def save_to_db(data: str) -> str:
"""
此函数将数据保存到数据库中。
"""
db = sqlite3.connect('data.db')
cursor = db.cursor()
cursor.execute(data)
db.commit()
db.close()
return "Data saved to database."
@tool
def get_from_db(data: str) -> str:
"""
此函数从数据库中获取数据。
"""
db = sqlite3.connect('data.db')
cursor = db.cursor()
cursor.execute(data)
result = cursor.fetchall()
db.close()
return result
tools = [interpreter, save_to_db, get_from_db, retriever_tool]
后面就可以使用这些tools,实现Agent的功能,让LLM执行外部任务,获取外部信息。
conversation_buffer_window_memory是一个用于存储对话历史的内存模块,可以用于实现对话的记忆功能。在这里,我们使用它来存储对话历史,实现对话的连贯性。
initialize_agent函数用于初始化一个Agent,可以指定tools,memory,verbose等参数,实现Agent的功能。
memory = ConversationBufferWindowMemory(memory_key='chat_history', k=3, return_messages=True)
chat_agent = initialize_agent(
agent='chat-conversational-react-description',
tools=tools,
memory=memory,
verbose=True,
max_interations=3,
llm=llm,
handle_prasing_error=True
)
query = "今天买菜花了30,买了一些蔬菜和水果。"
response = chat_agent.invoke(query)
print(response)
query = "到数据库中查询今天的花费"
response = chat_agent.invoke(query)
print(response)
项目成果与展示:
应用场景展示:
助手适合个人或家庭在生活中使用,以自然语言的方式记录每日花费,计算支出总额,理解支出的类型与特点,帮助用户更好的管理财务。
功能演示:
根据自然语言输入,用户将个人的每日花费,支出类型等信息输入到助手中,Agent执行外部海马,从外部数据库中CURD数据,个性化总结,理解用户,提供更好的建议。
问题与解决方案:
实际使用中,受限于最大Token和问题,LLM很可能不会使用外部的tools,导致回答偏离预期。
问题分析:
问题不够明确,没有主动引导LLM使用tools,导致LLM无法执行外部任务,获取外部信息。
解决措施:
强化prompt,绝对引导LLM使用tools,保证LLM执行外部任务,获取外部信息。
项目总结与展望:
项目评估:
技术实现初步达到预期,RAG模型能够在特定领域提供更加准确的回答,但是在数值理解和具体的财务分析上,还有待提高。
未来方向:
优化RAG模式,采取GraphRAG或更多rag方式,优化获取信息的模式,并存取更高质量的数据,提高对话的准确性。建立一个美观的前端界面,提供更好的用户体验。