一起学-基于 NIM 建构多模态 AI-Agent(词典助手)

1 篇文章 0 订阅

概要

NVIDIA AI-AGENT夏季训练营
项目名称:AI-AGENT夏季训练营 — RAG智能词典助手(多模态)
报告日期:2024年8月18日
项目负责人:赵冠齐

项目概述

技术栈

本项目基于NIM的模型接口,结合Langchain的链式编程,Gradio作为UI界面以及Funasr,Edge-tts等开源模型,实现了一套完整的RAG智能对话助手(多模态)

应用场景及亮点

本项目以构建一个智能词典助手为目标,通过多模态输入方式来实现查询词典的功能。
用户通过前端输入要查询的词语,可以是文字输入,或通过语音输入,甚至图片输入。

技术方案与实施步骤

模型选择:

本项目基于RAG构建,因此Embedding模型选择如下:

NVIDIAEmbeddings(model="nvidia/nv-embedqa-mistral-7b-v2")

在vectorstores,向量存储时,使用工具为FAISS

from langchain_community.vectorstores import FAISS

在调用llm模型时选择如下,由于llama-3.1-405b是当前llama最新最大模型,多方面生成效果都很优秀,因此优先选择:

ChatNVIDIA(model="meta/llama-3.1-405b-instruct", nvidia_api_key=nvapi_key, max_tokens=512)

考虑到多模态输入,在读取图片信息的时候,ai-phi-3-vision-128k-instruct对于图像分析有专门的优化,整体效果很好,因此优先选择:

ChatNVIDIA(model="ai-phi-3-vision-128k-instruct")

考虑到多模态输入,在音频输入时,选择了开源模型Funasr,由阿里达摩院出品,因为本次查询词典场景更加偏向中文环境,因此选择了对于中文能力更加优秀的Funasr,而不是Whisper。同时Funasr作为开源模型,可快速本地部署,对于低成本的测试也很友好:

from funasr import AutoModel

考虑到多模态输入,在音频输出时,选择了开源模型Edge-TTS,由微软出品,
同样可快速本地部署,节约成本。(由Day2培训时,老师推荐,因此就拿来测试了)

import edge_tts

数据的构建:

因为应用场景为查询词典助手,因此我选择了《现代汉语词典》(第7版)的txt文本作为数据源。以下为数据源来源:
https://github.com/CNMan/XDHYCD7th?tab=readme-ov-file

文本效果如下:
词典文本

优化: 拥有数据源后,需要进行数据的embeding以及后续的向量化存储,再此之前,我们先要对数据进行一定的预处理,优化他的存储结构,才能在后续的向量化检索中得到更加优秀的表现:
数据切分
在上图中我将文档按照规则切分,使词典中每一个词语作为个单独的数据块,并提取词语本身作为title,提取数据所在文件路径作为sources。
数据向量化
在上图中我们在切分文本后,考虑到模型的最大输入512,因此需要再次对文本进行大小上的限制二次切分,并将切分结果,结合之前的元数据,一起封装为可以直接embeding的document格式。
使用FAISS工具调用embeding模型将数据向量化并存储在本地作为向量库。
注:由于整个词典切分后块数过多,导致embeding时间过长。所以可以适当的减少文档中的数据条数

功能整合:

语音输入:
语音功能的实现策略参考了一下项目作为框架:
https://github.com/Ikaros-521/voice_talk_chatgpt
替换了其中的模型以及模型调用的方式。

方案流程为

  1. 前端输入录音文件,或直接语音输入。后端通过读取音频文件,调用Funasr模型来转化为文字。
  2. 将文本结合Prompt,调用LLM模型来进行回答。
  3. 后端获取回复后调用Edge-TTS模型将文本转化为语音输出。最终在前端呈现。

图片输入:
图片功能的实现,参考了培训day3的multi_agent_demo.ipynb作为框架:
替换了其中的模型以及模型调用的逻辑。

方案流程为

  1. 前端输入图片。后端读取图片,调用模型来解释图片中的物品信息(返回为英文)
  2. 调用LLM模型来翻译物品信息(转为中文),获取文本。
  3. 调用本地向量库进行搜索匹配数据,将匹配数据结合Prompt调用LLM模型进行总结,整合,润色。

实施步骤

环境搭建:

python环境推荐3.8+(本人使用的3.10)
其他库如下

gradio
openai
colorlog
scipy
langchain-nvidia-ai-endpoints
langchain_core
langchain
langchain-community
matplotlib
faiss-cpu
openai
# Funasr包含后续调用中使用的推理模型,切分模型等安装
Funasr
edge_tts

部署内容:

  1. 注册获取NIM平台,并获取模型API KEY
  2. Rag的数据向量化及本地向量库存储
  3. 部署Funasr及Edge-TTS模型
  4. 部署Gradio及相关配置

代码实现:

整个数据向量化及存储过程已在前面的章节展示了。
音频处理的核心代码如下图:

def send_msg(audio, text):
    try:
        # 创建一个临时文件来存储录制的音频数据
        output_file = "out/" + common.get_bj_time(4) + ".wav"
        if text:
            stt_ret = text
        if audio:
            # 创建一个示例的 int16 音频数据
            int16_audio_data = np.array(audio[1], dtype=np.int16)
            # 使用 scipy.io.wavfile 将数据保存为 WAV 文件
            wavfile.write(output_file, audio[0], int16_audio_data)
            res = model.generate(output_file)
            stt_ret = res[0]['text']
            logging.info(f"语音识别内容:{stt_ret}")

            # 数据回显
            text_input.value = stt_ret
        '''
        llm调用stt_ret
        返回值chat_ret
        '''
        chat_ret = chain1.invoke(stt_ret)
        logging.info(f"对话返回:{chat_ret}")
        audio_path = './'+change_wav_to_mp3(output_file)
        communicate = edge_tts.Communicate(chat_ret, VOICE)
        communicate.save_sync(audio_path)

        logging.info(f"合成音频输出在:{audio_path}")

        return audio_path, chat_ret

    except Exception as e:
        logging.error(f"Error processing audio: {str(e)}")
        return None

Funasr的模型配置组合如下:

model = AutoModel(model="paraformer-zh",  vad_model="fsmn-vad",  punc_model="ct-punc")

Prompt及chain的构建如下:

prompt1 = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a dictionary assistant, capable of helping answer questions"
            " related to the explanations of characters and words. Answer solely "
            "based on the following context:{context}",
        ),
        ("user", "{question}"),
    ]
)

chain1 = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt1
    | llm
    | StrOutputParser()
)

图片处理的核心代码如下:

def chart_agent_gr(image_b64, user_input):

    embedder = NVIDIAEmbeddings(model="nvidia/nv-embedqa-mistral-7b-v2")
    folderpath="./data/"
    store=FAISS.load_local(folderpath+"/nv_embedding", embedder,allow_dangerous_deserialization=True)
    retriever = store.as_retriever(search_type="mmr",search_kwargs={'k':10,'fetch_k':100})
    
    
    image_b64 = image2b64(image_b64)
    # Chart reading Runnable
    chart_reading = ChatNVIDIA(model="microsoft/phi-3-vision-128k-instruct")
    chart_reading_prompt = ChatPromptTemplate.from_template(
        'Tell me the objects recognized based on the following image data, only respond with nouns, do not describe, : <img src="data:image/png;base64,{image_b64}" />'
    )
    chart_chain = chart_reading_prompt | chart_reading

    translate_llm = ChatNVIDIA(model="meta/llama-3.1-405b-instruct")
    translate_prompt = ChatPromptTemplate.from_template(
        'Only translate the following English words into Chinese, do not reply with any other description:<content {content}>'
    )
    translate_chain = translate_prompt | translate_llm
    instruct_chat = ChatNVIDIA(model="meta/llama-3.1-405b-instruct")

    prompt1 = ChatPromptTemplate.from_messages(
      [
        (
            "system",
            "You are a dictionary assistant, capable of helping answer questions related to the explanations of characters and words. Answer solely based on the following context:\n<Documents>\n{context}\n</Documents>",
        ),
        ("user", "{question}"),
      ]
    )
    chain1 = (
        chart_chain
        | RunnableLambda(print_and_return)
        | translate_chain
    )
    chain2 = (
        {"context": retriever, "question": RunnablePassthrough()}
        | RunnableLambda(print_and_return)
        | prompt1
        | instruct_chat
        | StrOutputParser()
    )
    return chain2.invoke(chain1.invoke({"image_b64": image_b64}).content)

调用chain1来解读图片内容,并翻译图片内容从英文–>中文。
调用chain2来搜索本地向量库并将匹配内容交给LLM进行最终总结。

调试与优化

整个项目的多模态由于时间问题,拆分为了音频搜索,和图片搜索两个页面,独立功能都可以正常运行。
但没有来的及整合在同一页面下,完成最终的集成测试。

项目成果与展示

应用场景展示:

首先对比在没有使用RAG的方案下,大模型本身对于汉语生僻字查询的能力:
模型
请添加图片描述
是否正确?我们看一下正确答案
请添加图片描述
显然大模型没有正确回答问题,而且给了我们错误的导向

再来看一下加入了RAG方案的问答场景(首先是文字输入):
请添加图片描述
可以看到,在调用了同一个模型的情况下,RAG明显回答正确,并且还有语音播报。来看一下后台的日志:
在这里插入图片描述
程序将模型调用后的返回文字保存为out目录中的3.MP3

好的,我们来继续展示音频输入的效果如何:
请添加图片描述
首先我来对着麦克风说一段话,说了什么呢。在前端还在等待最终输出的时候,我们可以看一下后端服务打印的日志来理解,录音上传后的中间过程
请添加图片描述
程序成功的识别了我的录音内容,并将模型调用后的返回文字保存为out目录中的4.MP3
看来过程很顺利,这时,前端应该也开始播放我的搜索结果了:
请添加图片描述

好的,我们打开图片输入的页面(时间问题,两个输入模式没有统一在一个页面中):
请添加图片描述
那么识别什么呢,好像刚刚结束的奥运会,我们表现得还不错
请添加图片描述
看了下,好像跑通了,但是结果却没找到奥运五环。我们来看下后台的日志
请添加图片描述
可以看到,图片识别出了奥运五环,但是在向量库匹配时却只能找到奥运会的解释。所以最终输出没有对于奥运五环的解释。
那这样的搜索结果对么?去看看材料原文是什么样的:
请添加图片描述
看来RAG没有骗我,原文中确实没有奥运五环的解释。

问题与解决方案

问题:首先,基于我们效果展示的最后一个案例。在没有得到确切答案时,模型最终回复了没有找到答案。
解决方案:这样的回复显然不是很好,我们可以在PE工程中,添加对于没有找到答案的相应处理方式,再告知没有准确答案的同时可以推荐一些意思相近的词语的解释,或是跳过向量库提供的材料,自己回答。
这需要我们提供一套完备的prompt模板以及多次测试(尚未完成)

问题:对于文本过大时,切分策略值得我们思考,如何能让切分的效果最佳,不截断原来文本连贯的意思。同时保证搜索时,能返回切分后的多个文本块。
解决方案:这需要我们深入理解业务场景下文本的结构,在切分同时,我们可以提取所切文本的抽象信息,如标题,其中的关键字啊,所属段落啊等等来构成元数据metadata,再从搜索时加入一些元数据的匹配策略,从而更提高召回质量

项目总结与展望

项目评估:

项目整体完成了对于本次培训内容的实际应用以及场景构想。
成功的让我接触了很多以往没有测试过的模型。
熟悉了langchain的基于chain的快速搭建流程(熟悉了以后确实很方便)
实现了文字,图片,语音等多模态输入方式。

未来方向:

由于培训时间有限,对于RAG的很多内容,止步于搭建和测试。在实际中如召回的优化,数据的清洗,向量化等多个环节的优化讨论是缺少的。
因此,未来希望更多的从RAG的能力深入到RAG的质量。不断地优化,提高正确率。

附件与参考资料

  1. https://github.com/CNMan/XDHYCD7th?tab=readme-ov-file
  2. https://github.com/Ikaros-521/voice_talk_chatgpt
  3. https://github.com/rany2/edge-tts
  4. https://github.com/modelscope/FunASR/tree/main
  5. https://build.nvidia.com/explore/discover
  6. https://python.langchain.com/
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值