2024/08/15-2024/08/17,参加了为期三天的NVIDIA AI-AGENT夏季训练营,并完成RAG智能对话机器人项目和构建多模态智能体。
目录
LANGCHAIN-NVIDIA-AI-ENDPOINTS工具
将文档处理到 faiss vectorstore 并将其保存到磁盘
利用Microsoft Phi 3 vision 来解析图片数据
项目名称
基于NVIDIA NIM平台构建LLM-RAG和多模态智能体
项目概述
本项目展示了AI-Agent在论文撰写方面的应用。本项目可以用在项目研究初始阶段和论文撰写阶段。在项目研究初期,在论文数据库网站上检索到多篇相关论文后,通过融合专业领域论文与大模型,并引入RAG技术构建对话机器人,实现对目标研究方向成果及其实现路径的快速、深入洞察。在论文撰写阶段中,使用AI-Agent将数据表格自动生成图表,例如饼图、折线图、柱状图等,可以更加直观清晰的展示数据。
技术方案
模型选择
RAG对话机器人实验使用模型:microsoft/phi-3-small-128k-instruct
构建多模态智能体使用模型:microsoft/phi-3-vision-128k-instruct
NVIDIA Embeddings模型:NV-Embed-QA,用于文本向量化。
以上模型均可以在NVIDIA NIM平台(Try NVIDIA NIM APIs)上找到
数据的构建
1. 数据的获取,使用RAG实现对话机器人实验中的数据为,从论文数据库下载的pdf论文。
2. 文本的预处理,数据清洗(删除空行等)和分块处理。
3. 使用FAISS将分块后的文本向量化,并保存到磁盘。结合向量化模型NV-Embed-QA去做embedding处理,把文本信息变成向量,然后把embedding存储到Vector Database中,构建向量索引。
NVIDIA NIM 平台
NIM是一套易于使用的预构建容器化工具,是指一系列的、预先构建好的容器镜像的集合。
目的是帮助企业和开发者能够加速生成式AI的部署,其中包括了很多大模型,例如:Community AI Models、NVIDIA AI Foundation Models、Custom AI Models。
NIM平台对这些模型进行了加速和优化,基于triton加速推理服务器,基于TensorRT、TensorLLM、Pytorch这些推理引擎而构建。
LANGCHAIN-NVIDIA-AI-ENDPOINTS工具
langchain和nim结合使用的一个接口工具库
使用举例:导入一个工具类,输入交互语句并获取回答
(1)安装
pip install langchain
pip install -U langchain-nvidia-ai-endpoints
(2)通过getpass方式隐形的导入nim的API KEY,防止秘钥泄露
import getpass
import os
if os.environ.get("NVIDIA_API_KEY", "").startswith("nvapi-"):
print("Valid NVIDIA_API_KEY already in environment. Delete to reset")
else:
nvapi_key = getpass.getpass("NVAPI Key (starts with nvapi-): ")
assert nvapi_key.startswith("nvapi-"), f"{nvapi_key[:5]}... is not a valid key"
os.environ["NVIDIA_API_KEY"] = nvapi_key
(3)导入最核心的工具类,这个工具类里就封装了NIM和langchain之间去做结合的很多函数和方法。
from langchain_nvidia_ai_endpoints import ChatNVIDIA
# 查看当前这个工具能够支持的模型都有哪些
ChatNVIDIA.get_available_models()
# 输出
'''
[Model(id='01-ai/yi-large', model_type='chat', client='ChatNVIDIA', endpoint=None, aliases=['ai-yi-large'], supports_tools=False, supports_structured_output=False, base_model=None),
Model(id='databricks/dbrx-instruct', model_type='chat', client='ChatNVIDIA', endpoint=None, aliases=['ai-dbrx-instruct'], supports_tools=False, supports_structured_output=False, base_model=None),
........]
'''
(4)选择一个模型,输入与它交互的语句并获取回答
# 实例化大模型
llm = ChatNVIDIA(model="microsoft/phi-3-small-128k-instruct", nvidia_api_key=nvapi_key, max_tokens=512)
# 输入与它交互的语句
result = llm.invoke("红楼梦的作者是谁?")
print(result.content)
# 输出:
#《红楼梦》的作者是曹雪芹。这部小说是中国古典文学的巅峰之作,也是世界文学的宝库之作。据传说,曹雪芹在写作过程中,深受家族的衰败和个人的悲剧影响,这些情感都被巧妙地融入了小说的故事中,使得《红楼梦》不仅是一部文学作品,更是一部深刻的人文哲理作品。
RAG介绍与模型优势分析
RAG(Retrieval Augmented Generation),检索增强生成
为什么使用RAG?
- 预训练大模型有一定的知识局限性,知识不更新会过时
- 大多数企业或开发者难以进行大模型从头到尾的训练,算力不够,没有海量数据
- 大模型由于过拟合、泛化能力不足、训练数据有偏差、数据分布不均等,会出现幻觉
为了解决这些问题,RAG是一个不错的工程化手段,它可以把大语言模型和外部的知识源去进行一个结合,去解决大模型知识更新困难的问题,改进大模型回答的准确性。
RAG处理流程
一般分为三个步骤:
步骤一:
数据的获取(网络数据、In-domain/Private documentation)
文本的预处理(数据清洗等,例如网络数据会有<div>等)
文本向量化,结合向量化模型去做embedding处理,把文本信息变成向量,然后把embedding存储到Vector Database中,构建向量索引。
步骤二:知识的检索
当输入一个问题去检索的时候,也会通过一个embedding模型,把检索的问题做embedding,变成向量。
问题与数据-向量对向量,二者之间做向量相似度的计算,去找到和问题最相关的一批文档。文档的embedding就会作为Augmented prompt给大模型,让大模型在第三步去生成答案。
步骤三
把增强的Augmented prompt给大模型,大模型在该步骤就会把外部的知识去融合上下文,生成更加准确的答案。
(图片源自2024 NVIDIA AI-Agent训练营-day1课程PPT截图)
LLM-RAG交互流程图
(图片源自2024 NVIDIA AI-Agent训练营-day1课程PPT截图)
其中Embedding Model是非常重要的一块,有时候分数低,跟Embedding Model关系很大。它的效果好,RAG的准确度就会高很多。
AI-AGENT介绍
AI-AGENT是一种能够感知环境、进行决策和执行动作的智能实体或智能系统,可以自主执行任务、做出决策并与环境交互,无需人工干预。
多模态模型Phi-3-Version
Phi-3-Version是Phi-3系列中的第一个多模态模型。
- 它将文本和图像结合在一起,并具有推理现实世界图像以及从图像中提取和推理文本的能力。
- 它还针对图表和图解理解进行了优化,可用于生成见解和回答问题。
- Phi-3-Version以Phi-3-mini的语言功能为基础,继续在小型模型中整合强大的语言和图像推理质量。
应用场景
- OCR
- Image Captioning
- Table Parsing
- Figure Understanding
- Reading Comprehension on Scanned Documents
- Set-of-Mark Prompting
实验步骤
实验平台与开发环境
Jetson NX、JupyterLab
准备实验环境
搭建环境
1. 安装Miniconda
(1)从网站下载安装包:
Index of /anaconda/miniconda/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror
选择Miniconda3-py310_24.5.0-0-Windows-x86_64.exe,下载到本地后安装。
(2) 配置环境
1)安装后打开Anaconda Powershell Prompt(Minicoda3)
2)在终端输入以下命令:
# 创建Python3.9虚拟环境,名字为:ai_endpoint
conda create --name ai_endpoint python=3.9
# 进入虚拟环境
conda activate ai_endpoint
# 安装nvidia_ai_endpoint工具,这个工具是langchain和nim结合的一个Python的工具库
pip install langchain-nvidia-ai-endpoints
# 安装Jupyter Lab
pip install jupyterlab
# 安装langchain_core
pip install langchain_core
# 安装langchain
pip install langchain
# 安装matplotlib
pip install matplotlib
# 安装Numpy
pip install numpy
# 安装faiss, 如果没有GPU可安装CPU版本,这是一个开源的向量化的数据库
pip install faiss-cpu==1.7.2
# 安装openai库
pip install openai
打开JupyterLab
1. 安装上述环境
2. 启动Anaconda Powershell Prompt(Minicoda3),
1)可以cd 到指定路径下
2)在终端中输入“conda activate ai_endpoint”,其中ai_endpoint为之前创建的虚拟环境。
3)在终端输入jupyter-lab,可自动在浏览器中打开JupyterLab
准备API KEY
(1)创建NIM账户,获得一个API KEY
1)打开网页,Try NVIDIA NIM APIs ,创建账户,使用邮箱创建,创建后登录
2)任选一个模型,点击,并点击右下方“get API Key”按钮
3)在弹出的页面点击Generate Key按钮,生成API KEY后保存到本地,后续代码中会用到。
(2)测试网络及API KEY
运行以下代码,若输出正常,则网络正常,生成的API KEY可用
from openai import OpenAI
client = OpenAI(
base_url = "https://integrate.api.nvidia.com/v1",
api_key = "填写自己生成的API KEY"
)
completion = client.chat.completions.create(
model="meta/llama-3.1-405b-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="")
实验环境中隐性获取API KEY
import getpass
import os
if os.environ.get("NVIDIA_API_KEY", "").startswith("nvapi-"):
print("Valid NVIDIA_API_KEY already in environment. Delete to reset")
else:
nvapi_key = getpass.getpass("NVAPI Key (starts with nvapi-): ")
assert nvapi_key.startswith("nvapi-"), f"{nvapi_key[:5]}... is not a valid key"
os.environ["NVIDIA_API_KEY"] = nvapi_key
实验一:使用RAG实现对话机器人代码实现
模型选择
使用微软推出的小模型:microsoft/phi-3-small-128k-instruct
llm = ChatNVIDIA(model="microsoft/phi-3-small-128k-instruct", nvidia_api_key=nvapi_key, max_tokens=512)
初始化NV-Embed-QA向量模型
导入一个工具类NVIDIAEmbeddings,作用是到NIM平台当中找到对应的向量化模型
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
embedder = NVIDIAEmbeddings(model="NV-Embed-QA")
获取文本数据集
将需要添加的论文放在./zh_data/目录下,文件格式为pdf格式
(1)读取一篇预先下载好的pdf格式论文,并将数据添加到data列表中
安装:pip install pdfplumber
import os
from tqdm import tqdm
from pathlib import Path
import pdfplumber
# Here we read in the text data and prepare them into vectorstore
ps = os.listdir("./zh_data/")
data = []
sources = []
for p in ps:
if p.endswith('.pdf'):
path2file="./zh_data/"+p
with pdfplumber.open(path2file) as pdf:
for page in pdf.pages:
text = page.extract_text()
if text:
data.append(text)
sources.append(path2file)
else:
# 如果页面是空的,可以选择加入空字符串或跳过
data.append("")
(2)对读取的数据进行一些基本的清理并删除空行
documents=[d for d in data if d != '\n']
len(data), len(documents), data[0]
将文档处理到 faiss vectorstore 并将其保存到磁盘
1. 导包
# Here we create a vector store from the documents and save it to disk.
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
2. 存储
# 只需要执行一次,后面可以重读已经保存的向量存储
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('./zh_data/nv_embedding') # 保存到本地
CharacterTextSplitter:分词器,会把读进来的文档进行分词、分块的处理,可以有效的提高RAG检索准确度。
chunk_size:取决于embedding模型的Max_token是多少,chunk_size小于Max_token
store:相当于向量数据库
3. 重读之前处理并保存的 Faiss Vectore 存储
store = FAISS.load_local("./zh_data/nv_embedding", embedder,allow_dangerous_deserialization=True)
提出问题并进行RAG检索
def chat(input_text):
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()
)
return chain.invoke(input_text)
使用Gradio在UI界面中展示
安装gradio:pip install gradio
import gradio as gr
chat_app = gr.Interface(fn=chat,
inputs=["text"], # 输入类型为文本
outputs=["text"], # 输出类型也为文本
title="Text Echo App", # 界面标题
description="Enter some text", # 界面描述
)
# 启动界面
chat_app.launch(debug=True, share=True,show_api=False)
实验二:基于AI-Agent将表格数据自动生成图表代码实现
导入工具包
1. 导包
from langchain_nvidia_ai_endpoints import ChatNVIDIA
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnableLambda
from langchain.schema.runnable.passthrough import RunnableAssign
from langchain_core.runnables import RunnableBranch
from langchain_core.runnables import RunnablePassthrough
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
import os
import base64
import matplotlib.pyplot as plt
import numpy as np
本实验主要需要三个工具包:
langchain_nvidia_ai_endpoint
: 用来调用nvidia nim的计算资源langchain
: 用来构建对话链, 将智能体的各个组件串联起来base64
: 因为本实验是构建多模态的智能体, 需要base64来对图像进行编解码
2. API KEY
os.environ["NVIDIA_API_KEY"] = "自己生成的API KEY"
利用Microsoft Phi 3 vision 来解析图片数据
1. 将图片进行编解码
def image2b64(image_file):
with open(image_file, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
return image_b64
image_b64 = image2b64("table_data.png")
2. 基于 NIM 接口实现 Phi-3-Vision 的推理实践
将编码后的图像按照格式给到Microsoft Phi 3 vision , 利用其强大能力解析图片中的数据
# 多模态模型基于 NIM 的调用
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)
例如: test_data.png如图
经过上述步骤解析后为:
使用 LangChain 构建多模态智能体
Agent 应用场景:将图片中的统计图表转换为可以用 python 进行分析的数据
Agent 工作流:
- 接收图片,读取图片数据
- 对数据进行调整、分析
- 生成能够绘制图片的代码,并执行代码
- 根据处理后的数据绘制图表
接收图片 -> 分析数据 -> 修改数据 -> 生成绘制图片的代码 -> 执行代码 -> 展示结果
1. 定义一些辅助函数用于chat_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
2. 构建智能体agent
def chart_agent(image_b64, user_input, table):
# 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),
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
- chart_reading_prompt, 提示词模板,我们输入的图片会变成base64格式的string传输给它
- char_reading:将处理好的提示词输入给char_reading,也就是microsoft/phi-3-vision大模型来进行数据分析, 得到我们需要的表格或者说table变量
- 将Phi3 vision处理好的table和提示词输入给另一个大模型llama3.1, 修改数据并生成代码
3. 智能体使用示例:
(1)初始化
# 使用全局变量 table 来存储数据
table = None
(2) 修改表格中的内容
user_input = "replace table string's 'Natural Language Processing' with 'NLP1'"
chart_agent(image_b64, user_input, table)
print(table)
修改后如下:
基于 Gradio 框架建立前端互动界面
将多模态智能体封装进Gradio
1. 安装gradio:pip install gradio
2. 构建智能体,与上述chat_agent函数基本相同,增加一个图片编码语句:image_b64 = image2b64(image_b64),因为使用gradio时,gradio中上传图片的格式是png或jpg等图片格式,所以在最开始的步骤中加入了一个编码的过程。
代码如下:
(1)重新定义一个辅助函数
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
(2)构建智能体
def chart_agent_gr(image_b64, user_input):
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="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})
3. 执行下面代码, 将打开一个Gradio的服务, 我们可以利用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="Generate Diverse Charts Using an Agent",
description="Generate Diverse Charts Using an Agent",
allow_flagging="never")
multi_modal_chart_agent.launch(debug=True, share=True, show_api=False, server_port=5005, server_name="0.0.0.0")
4. 执行上述代码后,在浏览器输入: localhost:5005,可以打开如下界面
5. 上传要分析的表格图片(从电脑本地选取),在user_input框内输入提示词,点击Submit后即可显示生成的图表。
提示词示例:
生成簇状柱形图:draw this table as clustered bar chart in python, and save the image in path: D:\..\2024_summer_bootcamp\day3\table_data1.png
基于某一列生成饼状图:draw this table as pie chart base on 'ACM' in python, and save the image in path: D:\...\2024_summer_bootcamp\day3\table_data3.png
项目成果与展示
1. 应用场景展示:
收集多篇相关领域最新的研究论文,使用RAG+SLM获取到该领域内实现相关课题的方法等。
利用Agent分析论文中的表格,基于其中一列,生成饼状图
问题与解决方案
在进行实验时遇到了以下问题:
1. jupyterLab关闭后如何再次打开?
需要打开Anaconda Powershell Prompt(Miniconda3)终端,在终端输入“conda activate 虚拟环境名称 ”命令后,再输入jupyter-lab
2. 使用RAG技术时,在读取pdf文档数据时,如何将pdf文档内容读取到列表中?
本实验安装了pip install pdfplumber库,通过以下代码读取
with pdfplumber.open(path2file) as pdf:
for page in pdf.pages:
text = page.extract_text()
if text:
data.append(text)
3. 如何将获取的回答通过gradio显示在UI界面上?
使用gr.Interface来连接输入、输出和函数
chat_app = gr.Interface(fn=chat,
inputs=["text"], # 输入类型为文本
outputs=["text"], # 输出类型也为文本
title="Text Echo App", # 界面标题
description="Enter some text", # 界面描述
)
项目总结与展望
本次项目学习到了如何结合RAG技术和大模型构建对话机器人,如何基于NIM平台调用模型,调用模型后如何使用模型。如何使用 LangChain 构建多模态智能体,但对这一块内容还需要继续学习。未来继续尝试在RAG获取数据时添加获取网络文档数据,使用其他模型使得Agent处理更多类型的数据。
附件与参考资料
2024 NVIDIA开发者社区夏令营环境配置指南(Win & Mac)_csdn 2024nvidia开发者-优快云博客
新用户如何获取NIM API KEY.docx