每日花钱助手——RAG-Agent智能对话机器人

2024-08-17

项目名称:每日花钱助手——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方式,优化获取信息的模式,并存取更高质量的数据,提高对话的准确性。建立一个美观的前端界面,提供更好的用户体验。

附件与参考资料

Microsoft-Phi-3-NvidiaNIMWorkshop

Langchain doc