langchain 入门指南 - 实现一个多模态 chatbot

本文涉及的产品
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
简介: langchain 入门指南 - 实现一个多模态 chatbot

在前面的文章中,我们学会了如何通过 langchain 实现本地文档库的 QA,又或者通过 langchain 来实现对话式的问答系统。

在这篇文章中,我们将会学习如何通过 langchain 来实现一个多模态的 chatbot。

本文会构建一个有如下功能的 chatbot:

  • 可以生成图片
  • 可以回答用户的问题
  • 可以检索本地文档库中的信息
  • 可以从互联网进行搜索信息

什么是多模态

在前面的大部分例子中,我们跟 LLM 对话的时候都是使用了文本作为输入和输出。

但是除了文本,我们也可以让 LLM 来为我们生成图片。

多模态是指同时使用两种或两种以上的信息模式或表现形式。在人工智能和机器学习的背景下,

多模态通常指的是能够处理和融合不同类型数据的系统,这些数据可能包括文本、图像、音频、视频或其他传感器数据。

准备操作

  • 配置 OPENAI_API_KEYOPENAI_BASE_URL 环墋变量。
  • 配置 SERPER_API_KEY 环境变量,可以从 https://serper.dev 获取。

如和实现对本地文档的 QA

langchain 中,RetrievalQA 是一个结合了检索(Retrieval)和问答(QA)的组件。

它允许你构建一个系统,该系统能够根据用户的提问,从提供的文档或知识库中检索相关信息,并回答用户的问题。

RetrievalQA 的工作流程如下:

  • 检索(Retrieval):当用户提出一个问题时,RetrievalQA 会使用一个检索机制(本文会使用向量数据库做语义检索)
  • 阅读理解:一旦检索到相关的信息,RetrievalQA 会使用一个阅读理解模型来理解这些信息,并回答用户的问题。
  • 问答:最后,RetrievalQA 会使用一个问答模型(ChatModel)来生成最终的回答。

RetrievalQA 的优势在于它能够处理大量复杂的信息,并提供精确的答案。它特别适合那些需要从大量文档中检索信息的场景,例如法律文件、医学文献、技术手册等。

直接跟 LLM 对话的时候,一般都会有一个上下文大小限制的问题,太大的文档无法全部放入到上下文中。

但是可以先分片存入向量数据库中,在跟 LLM 对话之前,再从向量数据库中检索出相关的文档。最终发给 LLM 的数据只有相关的文档,这样就能够更好地回答用户的问题。

将 pdf 存入向量数据库

我们可以使用自己的 pdf 文档。

在这个例子中,我们将会使用 langchain 来将一个 pdf 文档存入向量数据库中:

from langchain_community.document_loaders import PyPDFLoader
# 加载 pdf 文档
loader = PyPDFLoader("Spotmax_intro_cn_2020.pdf")
docs = loader.load()
# 文档分片
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_spliter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
splits = text_spliter.split_documents(docs)
persist_directory = 'data/'
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
embedding = OpenAIEmbeddings()
# 创建向量数据库
vectordb = Chroma.from_documents(
    documents=splits,
    embedding=embedding,
    collection_name="spotmax",
    persist_directory=persist_directory,
)
# 持久化向量数据库
vectordb.persist()

说明:

  • PyPDFLoader 是一个用于加载 pdf 文档的类。
  • RecursiveCharacterTextSplitter 是一个用于将文档分片的类。
  • Chroma 是一个向量数据库类,用于存储和检索向量化的文档。
  • vectordbChroma 的一个实例,用于存储和检索文档。
  • vectordb.persist() 用于将向量数据库持久化到磁盘。

通过上面的代码,我们将会把 Spotmax_intro_cn_2020.pdf 文档存入到向量数据库中。

使用 RetrievalQA 进行问答

在上一步将 pdf 文档存入向量数据库之后,我们就可以通过 Chroma 的实例来对其做语义检索了。

def qa(question):
    from langchain_community.vectorstores import Chroma
    from langchain_openai import OpenAIEmbeddings
    embedding = OpenAIEmbeddings()
    vectordb = Chroma(persist_directory='data/', embedding_function=embedding, collection_name='spotmax')
    from langchain.chains.retrieval_qa.base import RetrievalQA
    from langchain_openai import ChatOpenAI
    llm = ChatOpenAI(
        model_name="gpt-3.5-turbo",
        temperature=0,
        max_tokens=200,
    )
    retriever = vectordb.as_retriever(
        search_type="mmr",
        search_kwargs={"k": 3}
    )
    qa0 = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever,
                                     return_source_documents=False, verbose=True)
    result = qa0({"query": question})
    return result['result']
print(qa("Spotmax 是什么?"))

说明:

  • vectordb 是从现有的 Chroma 向量数据库中加载的。
  • llm 是最终回答用户问题的大模型。
  • retriever 是用于检索文档的检索器,用户的问题会先通过检索器检索到相关的文档。
  • RetrievalQA.from_chain_type 创建一个 RetrievalQA 实例,用于回答用户的问题。
  • qa0({"query": question}) 用户的问题会先通过 retriever 检索到相关的文档,然后再交给 LLM,通过 llm 来回答用户的问题。

让 LLM 生成图片

这个比较简单,使用 OpenAIdall-e-2 模型即可:

def create_image(prompt):
    from openai import OpenAI
    client = OpenAI()
    response = client.images.generate(
        model='dall-e-2',
        prompt=prompt,
        size='256x256',
        quality='standard',
        n=1
    )
    u = response.data[0].url
    markdown_url = f"![image]({u})"
    return markdown_url

这个例子中,我们会根据用户的 prompt 生成一张 256x256 像素的图片,并且返回一个 markdown 链接形式的图片地址。

从互联网搜索信息

我们可以使用 GoogleSerperAPIWrapper 来从互联网搜索信息:

def query_web(question):
    """查询谷歌搜索结果"""
    from langchain_community.utilities import GoogleSerperAPIWrapper
    search = GoogleSerperAPIWrapper()
    return search.run(question)

如何让 chatbot 理解不同的操作?

我们可以使用 Agent 来让 chatbot 理解不同的操作:

  1. 将上面提供的几种操作封装成不同的 Tool
  2. 创建一个 AgentExecutor,根据用户的输入,选择合适的 Tool 来执行。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
    model_name="gpt-4",
    temperature=0.7,
    max_tokens=1000,
)
from langchain.agents import Tool
tools = [
    Tool(
        name="Get current info",
        func=query_web,
        description="""only invoke it when you need to answer question about realtime info.
            And the input should be a search query."""
    ),
    Tool(
        name="query spotmax info",
        func=qa,
        description="""only invoke it when you need to get the info about spotmax/maxgroup/maxarch/maxchaos.
            And the input should be the question."""
    ),
    Tool(
        name="create an image",
        func=create_image,
        description="""invoke it when you need to create an image.
            And the input should be the description of the image."""
    )
]
from langchain.memory import ConversationBufferWindowMemory
from langchain.agents import ZeroShotAgent, AgentExecutor
from langchain.chains.llm import LLMChain
prefix = """Have a conversation with a human, answering the following questions as best you can. You have access to the following tools:"""
suffix = """Begin!"
{chat_history}
Question: {input}
{agent_scratchpad}"""
prompt = ZeroShotAgent.create_prompt(
    tools,
    prefix=prefix,
    suffix=suffix,
    input_variables=["input", "chat_history", "agent_scratchpad"],
)
memory = ConversationBufferWindowMemory(k=10, memory_key="chat_history")
llm_chain = LLMChain(llm=llm, prompt=prompt)
agent = ZeroShotAgent(llm_chain=llm_chain, tools=tools)
agent_chain = AgentExecutor.from_agent_and_tools(
    agent=agent, tools=tools, verbose=True, memory=memory, handle_parsing_errors=True

说明:

  • 将前文提到的几种能力,封装为 AgentExecutor 可以使用的 Tool
  • 使用 llm 以及 tools 作为参数创建一个 AgentExecutor

AgentExecutor

在 LangChain 中,AgentExecutor 是一个组件,它负责执行一个代理(Agent)的推理循环。Agent 是一个更高级的组件,它可以根据输入动态选择和执行工具(Tools)。

Agent 通常用于构建更复杂的应用,其中 AI 模型需要根据上下文做出决策,选择合适的行动方案,并执行这些方案以达到某个目标。例如,一个 Agent 可能需要决定何时查询数据库,何时生成文本,或者何时调用外部 API。

AgentExecutor 的作用是作为一个执行环境,它接收用户的输入,然后根据 Agent 的策略或算法来指导 Agent 如何使用可用的工具来处理这个输入。代理会生成一个或多个动作(Actions),每个动作都对应一个工具的调用。

AgentExecutor 会执行这些动作,并可能根据动作的结果更新 Agent 的状态,然后返回最终的输出给用户。

如何跟 AgentExecutor 交互

直接使用 AgentExecutorinvoke 方法即可:

agent_chain.invoke(question)

调用 invoke 之后,AgentExecutor 会根据用户的输入,选择合适的 Tool 来执行,然根据 Tool 的输出进行下一步操作(调用其他 Tool 或者生成最终答案等)。

界面展示

我们最后可以使用 gradio 来构建一个简单的 web 界面:

import gradio as gr
with gr.Blocks() as demo:
    chatbot = gr.Chatbot(height=500) # 对话框
    msg = gr.Textbox(label="Prompt") # 输入框
    btn = gr.Button("Submit") # 按钮
    clear = gr.ClearButton(components=[msg, chatbot], value="Clear console") # 清除按钮
    btn.click(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
    msg.submit(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
gr.close_all()
demo.launch()

这个例子中,我们添加了一个 chatbot 组件,以及为用户提供了一个输入框和一个提交按钮。

inputsoutputs 参数用于指定输入和输出的组件。inputs 会作为参数传递给 respond 函数,respond 的返回值会被传递给 outputs 组件。

最终效果如下:

AgentExecutor 的处理过程如下(Thought -> Action -> Observation -> Thought -> Final Answer):

> Entering new AgentExecutor chain...
Thought: The question is asking for the current weather in Guangzhou and a male outfit recommendation. I can use the 'Get current info' tool to find the weather, and the 'create an image' tool to generate the outfit image.
Action: Get current info
Action Input: Guangzhou weather today
Observation: 94°F
Thought:The weather in Guangzhou is quite hot today. Now I need to think of an outfit that would be suitable for such warm weather.
Action: create an image
Action Input: A light summer outfit for men suitable for 94°F weather
Observation: ![image](https://oaidalleapiprodscus.blob.core.windows.net/private/org-GFz12lkhEotcvDvFYzePwrtK/user-1Ci7Ci1YNFjtlIO7AIY9aNux/img-zRsrd0cFFfxYAwW1oKZV9643.png?st=2024-07-24T05%3A29%3A33Z&se=2024-07-24T07%3A29%3A33Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-07-23T23%3A15%3A19Z&ske=2024-07-24T23%3A15%3A19Z&sks=b&skv=2023-11-03&sig=g9L0m2GHy%2BHtC48NPVDBjZWVGfrXGQzRam6XayUZvJ0%3D)
Thought:I now have the final answer.
Final Answer: 广州今天的天气很热,达到了94°F。我为你创建了一张适合这种天气的男士夏季轻便穿搭图。请参考图片中的服装搭配。![image](https://oaidalleapiprodscus.blob.core.windows.net/private/org-GFz12lkhEotcvDvFYzePwrtK/user-1Ci7Ci1YNFjtlIO7AIY9aNux/img-zRsrd0cFFfxYAwW1oKZV9643.png?st=2024-07-24T05%3A29%3A33Z&se=2024-07-24T07%3A29%3A33Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-07-23T23%3A15%3A19Z&ske=2024-07-24T23%3A15%3A19Z&sks=b&skv=2023-11-03&sig=g9L0m2GHy%2BHtC48NPVDBjZWVGfrXGQzRam6XayUZvJ0%3D)
> Finished chain.

我们可以看到在我提这个问题的时候,它做了如下操作:

  • 思考,然后发现需要获取今天广州的天气,这是 LLM 不懂的,所以使用了 Get current info 工具。
  • 获取到了天气信息之后,思考,然后发现需要生成一张图片,而我们有一个 create an image 工具,因此使用了这个工具来生成图片
  • 最终返回了今天广州的天气状况以及一张图片。

当然,我们也可以问它关于本地知识库的问题,比如 “什么是 spotmax?”(根据你自己的 pdf 提问,这里只是一个示例)

完整代码

最终完整的代码如下:

  • qa 函数用于回答用户关于本地知识库的问题
  • create_image 函数用于生成图片
  • query_web 函数用于从互联网搜索信息
  • respond 函数用于处理 chatbot 的对话响应
  • agent_chain 是一个 AgentExecutor 实例,用于执行 Agent 的推理循环
import gradio as gr
def qa(question):
    from langchain_community.vectorstores import Chroma
    from langchain_openai import OpenAIEmbeddings
    embedding = OpenAIEmbeddings()
    vectordb = Chroma(persist_directory='data1/', embedding_function=embedding, collection_name='spotmax')
    from langchain.chains.retrieval_qa.base import RetrievalQA
    from langchain_openai import ChatOpenAI
    llm = ChatOpenAI(
        model_name="gpt-3.5-turbo",
        temperature=0,
        max_tokens=200,
    )
    retriever = vectordb.as_retriever(
        search_type="mmr",
        search_kwargs={"k": 3}
    )
    qa0 = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever,
                                     return_source_documents=False, verbose=True)
    result = qa0({"query": question})
    return result['result']
def create_image(prompt):
    from openai import OpenAI
    client = OpenAI()
    response = client.images.generate(
        model='dall-e-2',
        prompt=prompt,
        size='256x256',
        quality='standard',
        n=1
    )
    u = response.data[0].url
    markdown_url = f"![image]({u})"
    return markdown_url
def query_web(question):
    """查询谷歌搜索结果"""
    from langchain_community.utilities import GoogleSerperAPIWrapper
    search = GoogleSerperAPIWrapper()
    return search.run(question)
def respond(message, chat_history):
    """对话函数"""
    bot_message = get_response(message)
    chat_history.append((message, bot_message))
    return "", chat_history
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
    model_name="gpt-4",
    temperature=0.7,
    max_tokens=1000,
)
from langchain.agents import Tool
tools = [
    Tool(
        name="Get current info",
        func=query_web,
        description="""only invoke it when you need to answer question about realtime info.
            And the input should be a search query."""
    ),
    Tool(
        name="query spotmax info",
        func=qa,
        description="""only invoke it when you need to get the info about spotmax/maxgroup/maxarch/maxchaos.
            And the input should be the question."""
    ),
    Tool(
        name="create an image",
        func=create_image,
        description="""invoke it when you need to create an image.
            And the input should be the description of the image."""
    )
]
from langchain.memory import ConversationBufferWindowMemory
from langchain.agents import ZeroShotAgent, AgentExecutor
from langchain.chains.llm import LLMChain
prefix = """Have a conversation with a human, answering the following questions as best you can. You have access to the following tools:"""
suffix = """Begin!"
{chat_history}
Question: {input}
{agent_scratchpad}"""
prompt = ZeroShotAgent.create_prompt(
    tools,
    prefix=prefix,
    suffix=suffix,
    input_variables=["input", "chat_history", "agent_scratchpad"],
)
memory = ConversationBufferWindowMemory(k=10, memory_key="chat_history")
llm_chain = LLMChain(llm=llm, prompt=prompt)
agent = ZeroShotAgent(llm_chain=llm_chain, tools=tools, verbose=True, handle_parsing_errors=True)
agent_chain = AgentExecutor.from_agent_and_tools(
    agent=agent, tools=tools, verbose=True, memory=memory, handle_parsing_errors=True
)
def get_response(message):
    res = agent_chain.invoke(message)
    return res['output']
with gr.Blocks() as demo:
    chatbot = gr.Chatbot(height=500) # 对话框
    msg = gr.Textbox(label="Prompt") # 输入框
    btn = gr.Button("Submit") # 按钮
    clear = gr.ClearButton(components=[msg, chatbot], value="Clear console") # 清除按钮
    btn.click(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
    msg.submit(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])
gr.close_all()
demo.launch()

总结

虽然 OpenAI 提供了 function calling 的特性,但是直接使用起来还是比较麻烦,通过 AgentExecutor 结合 tools 的方式,可以更好地组织和管理 chatbot 的能力。

在这篇文章中,我们学习了如何通过 langchain 来实现一个多模态的 chatbot,它可以生成图片、回答用户的问题、检索本地文档库中的信息、从互联网搜索信息等。


相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
目录
相关文章
|
14天前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
109 2
|
3月前
langchain 入门指南 - 使用 Agent
langchain 入门指南 - 使用 Agent
133 0
|
3月前
|
安全 API 数据库
langchain 入门指南 - 函数调用
langchain 入门指南 - 函数调用
112 1
|
3月前
|
机器学习/深度学习
langchain 入门指南 - In-context Learning
langchain 入门指南 - In-context Learning
80 0
|
3月前
langchain 入门指南 - 链式请求
langchain 入门指南 - 链式请求
65 0
|
14天前
|
人工智能 前端开发 JavaScript
前端大模型入门(二):掌握langchain的核心Runnable接口
Langchain.js 是 Langchain 框架的 JavaScript 版本,专为前端和后端 JavaScript 环境设计。最新 v0.3 版本引入了强大的 Runnable 接口,支持灵活的执行方式和异步操作,方便与不同模型和逻辑集成。本文将详细介绍 Runnable 接口,并通过实现自定义 Runnable 来帮助前端人员快速上手。
|
3月前
|
前端开发 人机交互
langchain 入门指南 - ReAct 模式
langchain 入门指南 - ReAct 模式
83 1
|
3月前
|
存储 人工智能 缓存
langchain 入门指南 - 让 AI 记住你说过的话
langchain 入门指南 - 让 AI 记住你说过的话
99 1
|
3月前
|
人工智能 搜索推荐 API
langchain 入门指南 - 让 AI 从互联网获取信息
langchain 入门指南 - 让 AI 从互联网获取信息
75 1
|
3月前
|
Linux
langchain 入门指南 - 使用提示词模板
langchain 入门指南 - 使用提示词模板
149 1