RAG多模态AI Agent模型:AI的‘超级搜索引擎’,但能说话的那种

1.项目概述

本文通过rag和多模态代理模型完成人机交互对话并可以接受图片并分析的任务

RAG(Retrieval-Augmented Generation)对话模型是一种先进的自然语言处理技术,旨在提升对话系统的准确性和智能水平。通过结合信息检索和生成模型,RAG对话模型能够提供更加丰富和相关的回答,特别是在处理需要广泛知识和细致背景的复杂对话时表现尤为出色。

多模态代理(Multi-Modal Agents)可以通俗理解为一种智能系统,它像一个全能助手,能够理解和处理来自不同来源的信息。例如,它不仅能听懂你说的话,还能看你指的东西,甚至理解你所做的动作。这样,它就能更全面地理解你的需求,给出更准确的回应。就像一个聪明的助手,它不仅听你说话,还能看你做的事情,帮助你完成任务。

2.技术方案与实施步骤

模型选择

目前,已经有多种深度学习模型可以实现人机对话问答交互。按照参数和计算需求可以分为大模型和小模型。大模型的优势在于能够处理复杂的任务,生成高质量的回答或进行复杂的推理。缺点就是:需要大量的训练数据来发挥最佳性能。训练和推理过程中需要强大的计算能力和存储资源。小模型反之。
本项目使用的llm模型就属于大模型的一种,准确度高但占用内存较多。因此使用了phi-3-small-128k-instruct小模型,在具体测试中发现,小模型输出结果有时会出现幻觉,也就是答非所问的情况,于是采用rag模型对小模型进行修正。

数据构建

下图是rag模型的工作流程。
请添加图片描述
个人上传的文本资料通过预处理之后,被embedding模型拆分,再储存到向量仓库中。
当用户端提出问题时,得到的答案会先根据向量仓库搜索,再得到结果。以此达到修正小模型的结果。
我认为rag比较有优势的一点就在于它可以通过添加文献,打造出属于自己的模型。

功能整合

实现功能1:通过rag模型修正小模型microsoft/phi-3-small-128k-instruct,将这个小模型完成对话任务的准确率提高。
实现功能2:通过Microsoft Phi 3 vision模型和LangChain 框架,创建一个可以分析图片信息的UI交互界面。

3.实施步骤

环境搭建

以下内容都是在Windows系统进行的
  1. 安装anaconda
    参考下面链接:anaconda安装

  2. 在anaconda中下载所需的库
    参考下面链接:配置所需库

代码实现

  1. 进入nvidia网站:NIM主页
    点击任意模型获取api_key

在这里插入图片描述
注意:api_key每次重新生成后,之前的api_key会失效

  1. 测试api_key
# 简易文字llm生成实例
# 测试api_key
from openai import OpenAI

client = OpenAI(
  base_url = "https://integrate.api.nvidia.com/v1",
  api_key = "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"
)

completion = client.chat.completions.create(
  model="databricks/dbrx-instruct",
  messages=[{"role":"user","content":"如何通俗理解玻色爱因斯坦凝聚态"}],
  temperature=0.2,
  top_p=0.7,
  max_tokens=1024,
  stream=True
)

for chunk in completion:
  if chunk.choices[0].delta.content is not None:
    print(chunk.choices[0].delta.content, end="")

生成结果:

玻色爱因斯坦凝聚 (Bose-Einstein Condensation)
是一种物质态,通俗地理解,它是指在绝对零度(−273.15℃)附近,玻色子(一种基本粒子)会聚集在一起,形成一个宏观量子态。这种凝聚态具有很多特殊的性质,例如,它可以形成超流体,流动时不会产生任何摩擦,这在经典物理学中是不可能出现的。玻色爱因斯坦凝聚态的发现,对于我们理解量子力学和统计物理学有着重要的意义。

这一步的代码可以在NIM主页随意替换模型

  1. 获取可用模型简介
from langchain_nvidia_ai_endpoints import ChatNVIDIA
ChatNVIDIA.get_available_models()
  1. 选用小模型和embedding模型
# 小模型更容易出现幻觉
llm = ChatNVIDIA(model="microsoft/phi-3-small-128k-instruct", nvidia_api_key=nvapi_key, max_tokens=512)
result = llm.invoke("如何通俗理解玻色爱因斯坦凝聚态")

print(result.content)
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
embedder = NVIDIAEmbeddings(model="ai-embed-qa-4")
  1. 丰富数据库
import os
from tqdm import tqdm
from pathlib import Path
from operator import itemgetter
from langchain.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain.text_splitter import CharacterTextSplitter
from langchain_nvidia_ai_endpoints import ChatNVIDIA
import faiss

ps = os.listdir("./zh_data/")
data = []
sources = []
for p in ps:
    if p.endswith('.txt'):
        path2file="./zh_data/"+p
        with open(path2file,encoding="utf-8") as f:
            lines=f.readlines()
            for line in lines:
                if len(line)>=1:
                    data.append(line)
                    sources.append(path2file)
                    
# 把整个文本分批embedding存到向量库,提高rag检索准确度 chunk_size:200-1000,取决于embedding模型的touken
text_splitter = CharacterTextSplitter(chunk_size=400, separator=" ")    
docs = []
metadatas = []

for i, d in enumerate(documents):
    splits = text_splitter.split_text(d)
    #print(len(splits))
    docs.extend(splits)
    metadatas.extend([{"source": sources[i]}] * len(splits))

store = FAISS.from_texts(docs, embedder , metadatas=metadatas)
store.save_local('向量数据库保存路径')                    
  1. 结合数据库进行问答
# 结合文档纠正小模型,消除幻觉
retriever = store.as_retriever()

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "Answer solely based on the following context:\n<Documents>\n{context}\n</Documents>",
        ),
        ("user", "{question}"),
    ]
)

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

chain.invoke("如何通俗理解玻色爱因斯坦凝聚态")
  1. 将图片进行编解码
from PIL import Image

def image2b64(image_file):
    with open(image_file, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode()
        return image_b64

image_b64 = image2b64("economic-assistance-chart.png")
display(Image.open("economic-assistance-chart.png"))
  1. 以Microsoft Phi 3 vision模型为例,解析图片中的数据
chart_reading = ChatNVIDIA(model="microsoft/phi-3-vision-128k-instruct")
result = chart_reading.invoke(f'Generate underlying data table of the figure below, : <img src="data:image/png;base64,{image_b64}" />')
print(result.content)

9.用LangChain构建多模态agent
下面的函数用于执行显示输出,执行代码等功能

import re

# 将 langchain 运行状态下的表保存到全局变量中
def save_table_to_global(x):
    global table
    if 'TABLE' in x.content:
        table = x.content.split('TABLE', 1)[1].split('END_TABLE')[0]
    return x

# helper function 用于Debug
def print_and_return(x):
    print(x)
    return x

# 对打模型生成的代码进行处理, 将注释或解释性文字去除掉, 留下pyhon代码
def extract_python_code(text):
    pattern = r'```python\s*(.*?)\s*```'
    matches = re.findall(pattern, text, re.DOTALL)
    return [match.strip() for match in matches]

# 执行由大模型生成的代码
def execute_and_return(x):
    code = extract_python_code(x.content)[0]
    try:
        result = exec(str(code))
        #print("exec result: "+result)
    except ExceptionType:
        print("The code is not executable, don't give up, try again!")
    return x

# 将图片编码成base64格式, 以方便输入给大模型
def image2b64(image_file):
    with open(image_file, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode()
        return image_b64

下面的函数用于定义多模态数据分析 Agent

def chart_agent(image_b64, user_input, table):
    # Chart reading Runnable
    chart_reading = ChatNVIDIA(model="ai-phi-3-vision-128k-instruct")
    chart_reading_prompt = ChatPromptTemplate.from_template(
        'Generate underlying data table of the figure below, : <img src="data:image/png;base64,{image_b64}" />'
    )
    chart_chain = chart_reading_prompt | chart_reading

    # Instruct LLM Runnable
    # instruct_chat = ChatNVIDIA(model="nv-mistralai/mistral-nemo-12b-instruct")
    # instruct_chat = ChatNVIDIA(model="meta/llama-3.1-8b-instruct")
    #instruct_chat = ChatNVIDIA(model="ai-llama3-70b")
    instruct_chat = ChatNVIDIA(model="meta/llama-3.1-405b-instruct")

    instruct_prompt = ChatPromptTemplate.from_template(
        "Do NOT repeat my requirements already stated. Based on this table {table}, {input}" \
        "If has table string, start with 'TABLE', end with 'END_TABLE'." \
        "If has code, start with '```python' and end with '```'." \
        "Do NOT include table inside code, and vice versa."
    )
    instruct_chain = instruct_prompt | instruct_chat

    # 根据“表格”决定是否读取图表
    chart_reading_branch = RunnableBranch(
        (lambda x: x.get('table') is None, RunnableAssign({'table': chart_chain })),
        (lambda x: x.get('table') is not None, lambda x: x),
        lambda x: x
    )
    # 根据需求更新table
    update_table = RunnableBranch(
        (lambda x: 'TABLE' in x.content, save_table_to_global),
        lambda x: x
    )
    # 执行绘制图表的代码
    execute_code = RunnableBranch(
        (lambda x: '```python' in x.content, execute_and_return),
        lambda x: x
    )

    chain = (
        chart_reading_branch
        #| RunnableLambda(print_and_return)
        | instruct_chain
        #| RunnableLambda(print_and_return)
        | update_table
        | execute_code
    )

    return chain.invoke({"image_b64": image_b64, "input": user_input, "table": table}).content
# 使用全局变量 table 来存储数据
table = None
# 将要处理的图像转换成base64格式
image_b64 = image2b64("economic-assistance-chart.png")

#展示读取的图片
from PIL import Image
display(Image.open("economic-assistance-chart.png"))

# 将图片的数据转为字符串
user_input = "show this table in string"
chart_agent(image_b64, user_input, table)
print(table)    # let's see what 'table' looks like now

# 让 Agent 自己尝试修改其中的内容
user_input = "replace table string's 'UK' with 'United Kingdom'"
chart_agent(image_b64, user_input, table)
print(table)    # let's see what 'table' looks like now
  1. 给智能体添加一个UI界面
# img_path是图片生成的路径
global img_path
img_path ='C:/Users/13116/2024_summer_bootcamp/image.png'
print(img_path)

def execute_and_return_gr(x):
    code = extract_python_code(x.content)[0]
    try:
        result = exec(str(code))
        #print("exec result: "+result)
    except ExceptionType:
        print("The code is not executable, don't give up, try again!")
    return img_path
    
# 解码图片    
def chart_agent_gr(image_b64, user_input, table):
    image_b64 = image2b64(image_b64)
    # Chart reading Runnable
    chart_reading = ChatNVIDIA(model="microsoft/phi-3-vision-128k-instruct")
    chart_reading_prompt = ChatPromptTemplate.from_template(
        'Generate underlying data table of the figure below, : <img src="data:image/png;base64,{image_b64}" />'
    )
    chart_chain = chart_reading_prompt | chart_reading

    # Instruct LLM Runnable
    # instruct_chat = ChatNVIDIA(model="nv-mistralai/mistral-nemo-12b-instruct")
    # instruct_chat = ChatNVIDIA(model="meta/llama-3.1-8b-instruct")
    #instruct_chat = ChatNVIDIA(model="ai-llama3-70b")
    instruct_chat = ChatNVIDIA(model="meta/llama-3.1-405b-instruct")

    instruct_prompt = ChatPromptTemplate.from_template(
        "Do NOT repeat my requirements already stated. Based on this table {table}, {input}" \
        "If has table string, start with 'TABLE', end with 'END_TABLE'." \
        "If has code, start with '```python' and end with '```'." \
        "Do NOT include table inside code, and vice versa."
    )
    instruct_chain = instruct_prompt | instruct_chat

    # 根据“表格”决定是否读取图表
    chart_reading_branch = RunnableBranch(
        (lambda x: x.get('table') is None, RunnableAssign({'table': chart_chain })),
        (lambda x: x.get('table') is not None, lambda x: x),
        lambda x: x
    )
    
    # 根据需求更新table
    update_table = RunnableBranch(
        (lambda x: 'TABLE' in x.content, save_table_to_global),
        lambda x: x
    )

    execute_code = RunnableBranch(
        (lambda x: '```python' in x.content, execute_and_return_gr),
        lambda x: x
    )
    
    # 执行绘制图表的代码
    chain = (
        chart_reading_branch
        | RunnableLambda(print_and_return)
        | instruct_chain
        | RunnableLambda(print_and_return)
        | update_table
        | execute_code
    )

    return chain.invoke({"image_b64": image_b64, "input": user_input, "table": table})

下面创建一个Gradio交互界面

import gradio as gr
multi_modal_chart_agent = gr.Interface(fn=chart_agent_gr,
                    inputs=[gr.Image(label="Upload image", type="filepath"), 'text'],
                    outputs=['image'],
                    title="Multi Modal chat agent",
                    description="Multi Modal chat agent",
                    allow_flagging="never")

multi_modal_chart_agent.launch(debug=True, share=False, show_api=False, server_port=5000, server_name="0.0.0.0")

4.项目成果

应用场景

在物理教育领域,RAG(Retrieval-Augmented Generation)技术可以显著提升学习和教学体验。举个例子,学生在学习物理时遇到难题,可以通过RAG系统提问。系统会从教材和科学文献中检索相关信息,生成详尽的解释和示例,这样学生可以得到个性化的辅导。此外,RAG也可以用于生成实验指导和解题步骤,提供复杂问题的解决方案,帮助学生更好地理解实验和理论。同时,它还能根据学生的学习进度推荐相关资源,并自动生成测试题和练习题。总之,RAG通过整合和生成信息,能够为学生提供更加精准和及时的学习支持。
多模态代理能够通过整合各种信息源来丰富学习体验。图片分析技术可以辅助理解复杂的物理现象。在教学过程中,系统可以通过分析图像展示的物理实验或演示模型,结合文本描述生成解释。例如,学生观看一个关于力学实验的视频,系统不仅能识别实验中的物体和动作,还能结合这些视觉信息生成详细的解释,帮助学生理解力的作用和效果。

功能演示

经过rag模型训练后得到的文本回答结果,得到的内容更精确:

玻色凝聚态是一种物质状态,其中粒子(如原子或离子)以非常低的温度聚集在一起,形成一个量子力学描述的单一物质。这种状态得名于物理学家尤里·玻色,他在1925年提出了玻色-爱因斯坟统计,描述了在低温下粒子的行为。在玻色凝聚态中,粒子的波函数相互重叠,形成一个称为量子涨落的量子态。这些量子涨落可以相互作用,导致粒子在整个系统中相互连接。这种连接使得粒子的行为与整个系统的行为相同,从而产生出一种集体行为。玻色凝聚态的一个例子是超导体,它们在低温下表现出零电阻和零磁阻。这是因为在超导体中,电子可以通过量子涨落相互连接,从而允许电流在没有阻力的情况下流动。另一个例子是超流体,它们在低温下表现出无摩擦流动。这是因为在超流体中,原子可以通过量子涨落相互连接,从而允许流体在没有摩擦的情况下流动。总之,玻色凝聚态是一种物质状态,其中粒子以非常低的温度聚集在一起,形成一个量子力学描述的单一物质。这种状态的一个例子是超导体和超流体,它们在低温下表现出零电阻和无摩擦流动。

下面是图片分析功能
在这里插入图片描述

5.问题与解决方案

  • 遇到的问题

使用api_key时报错显示api_key过期且积分不够

在这里插入图片描述在这里插入图片描述

  • 解决
    NIM主页网站中,点击右上角查看剩余Credits数量,发现Credits用光。
    需要重新注册nvidia账号。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值