使用LLM和LangChain构建聊天机器人🦜🔗

更新时间:2023/10/12, 10:26

在本文中,我将分享我在**Dash Company** 工作时构建聊天机器人的经验。我们的目标是深入探索Langchain,涵盖广泛的常见主题。此外,我还将分享我在构建聊天机器人方面的个人经历,该机器人可以与自定义API无缝集成以协助用户,并根据他们的网店数据提供见解建议。

背景

我将给您一个我们构建的聊天机器人的总体概念,然后我将逐步详细说明。

它是一个AI助手,可帮助用户分析他们的网店数据,并提供建议告诉他们如何改进工作室,在哪里可以花更多钱来提高网店收入等。

聊天机器人读取API swagger文件,并根据用户的问题决定需要使用哪个端点从应用后端获取数据以分析数据并给用户完美的答案。

聊天机器人后端结构

概述

  • 什么是LLM?

  • 什么是Langchain?

  • 为什么使用Langchain?

  • Langchain代理和链。

  • Langchain内存。

  • Langchain工具。

  • 构建聊天机器人。

  • 计划员和聊天代理。

  • 代理作为工具。

什么是LLM?

LLM或大型语言模型是专为通过分析和学习海量文本数据的模式来处理和生成人类文本而设计的高级人工智能模型。这些模型以能够以连贯和情境相关的方式理解、生成和操作语言的能力而闻名。

LLM的一个显着能力是其适应各种语言相关任务的能力,包括但不限于:

  1. 文本生成:根据给定的提示,它们可以生成连贯的、情境适宜的文本。

  2. 语言翻译:LLM能够将文本从一种语言翻译成另一种语言。

  3. 文本摘要:它们可以将更长的文本段落浓缩成简洁的摘要。

  4. 问答:LLM可以理解问题并根据其训练数据提供相关答案。

  5. 文本补全:它们可以预测并建议句子中的下一个单词或短语。

  6. 语言理解:LLM可以理解文本的含义和上下文,即使处理复杂的句子也能理解。

有免费和付费的LLM,流行的LLM示例:

  1. GPT-3(生成式预训练转换器3):由OpenAI开发,GPT-3是最著名、最强大的LLM之一。它拥有1750亿个参数,使其对广泛的自然语言处理任务非常通用。GPT-3可以生成连贯的、与上下文相关的文本、回答问题、翻译语言、模拟对话等。

  2. Falcon LLM:阿布扎比技术创新研究所的开源模型,在全球开源AI模型中排名第一。在100万亿个标记上训练,具有400亿个参数,Falcon的成功归因于使用独特管道进行高质量数据提取。结果数据集RefinedWeb提供了提炼的内容,Falcon的微调架构在训练和推理中都实现了令人印象深刻的效率,与GPT-3相比,在降低计算资源的同时表现出色,超过了GPT-3。

  3. LLaMA(大型语言模型Meta AI):这是一组涵盖7B到65B参数的最先进基础语言模型。这些模型体积更小,性能却非常好,大大减少了实验新方法、验证他人工作以及探索创新用例所需的计算能力和资源。

我们决定在我们的聊天机器人中使用GPT LLM。

什么是Langchain?

LangChain是一个强大的工具,可用于使用大型语言模型(LLM)。LLM本质上是非常通用的,这意味着尽管它们可以有效执行许多任务,但可能无法对需要深入的领域知识或专业知识的问题或任务提供具体的答案。例如,想象一下,您想使用LLM来回答有关特定领域的问题,如医学或法律。尽管LLM可以回答该领域的一般问题,但它可能无法提供需要专业知识或专业技能的更详细或微妙的答案。

LangChain的增长速度相当快,而且毫无疑问令人印象深刻!

为什么使用Langchain?

我们决定使用Langchain,以避免下沉到低级别并直接使用OpenAI API。

LangChain是一个框架,使开发人员能够构建可以推理问题并将其分解为更小子任务的代理。使用LangChain,我们可以通过创建中间步骤并将命令链接在一起向补全引入上下文和记忆。

LLM存在局限性。 为了解决这个限制,LangChain提供了一种有用的方法,其中文本语料库通过将其分解成块或摘要并在问到问题时搜索相似块来进行预处理。 这种预处理、实时收集和与LLM交互的模式是常见的,也可用于其他场景,例如代码和语义搜索。

所以归根结底,如果我们直接使用Open Ai Api,我们将需要从零开始构建所有提示,构建我们的解决方案以克服局限性,并自己构建总结和记忆工具,如果LangChain提供了所有这些工具来管理提示和局限性,为什么我们需要这样做呢?

Langchain链和代理

代理和链

在LangChain中,是至关重要的核心。 这些一个或多个LLM之间的逻辑连接是LangChain功能的支柱。 链可以从简单到复杂不等,这取决于需要和所涉及的LLM。

让我们构建一个简单的链,以便您了解链的概念...

from langchain.prompts import PromptTemplate
from langchain.llms import HuggingFace
from langchain.chains import LLMChain
 
prompt = PromptTemplate(
    input_variables=["city"],
    template="Describe a perfect day in {city}?",
)
 
llm = HuggingFace(
          model_name="gpt-neo-2.7B", 
          temperature=0.9)
 
llmchain = LLMChain(llm=llm, prompt=prompt)
llmchain.run("Paris")

首先,我们创建提示模板并添加变量链。我们将从人类问题中获取它,并将其传递给模板,然后将此消息发送到LLM。

在LangChain中,代理提供了一个创新的方法来根据用户输入动态调用LLM。 它们不仅可以访问LLM,还可以访问一套工具(如Google搜索、Python REPL、数学计算器、天气API等),这些工具可以与外部世界交互。

from langchain.agents import initialize_agent, AgentType, load_tools
from langchain.llms import OpenAI
 
llm = OpenAI(temperature=0)
tools = load_tools(["pal-math"], llm=llm)
 
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
 
response = agent.run("If my age is half of my dad's age and he is going to be 60 next year, what is my current age?")
print(response)  # Outputs: "My current age is 29.5 years old."

在这种情况下,代理利用pal-math工具和OpenAI LLM来解决嵌入在自然语言提示中的数学问题。 它演示了一个实际案例,其中代理通过理解提示,选择正确的工具来解决任务,并最终返回有意义的响应,从而带来了额外的价值。

Langchain内存

当您希望记住先前输入的项目时,内存就很方便。 例如:如果您询问“谁是阿尔伯特·爱因斯坦?”然后随后问“他的导师是谁?”,那么会话记忆将帮助助手记住“他”指的是“阿尔伯特·爱因斯坦”。

实现记忆步骤:

  1. 首先,您需要在提示中添加记忆
prefix = """
Your an market specialities to help user to analyze their data and assist them
"""

suffix = """Begin!"

{chat_history}
Question: {input}
{agent_scratchpad}"""

如您所见,我们添加了一个名为chat_history的变量,该变量将获取聊天的摘要。

  1. 总结聊天记录
messages=[]
chat_history = ChatMessageHistory(messages=messages)
memory = ConversationSummaryBufferMemory(llm=llm, chat_memory=chat_history, input_key="input", memory_key="chat_history")
  1. 将聊天摘要添加到您的代理中
llm_chain = LLMChain(llm=llm, prompt=prompt, memory=memory)

agent = ZeroShotAgent(
    llm_chain=llm_chain, 
    tools=tools, 
    verbose=True,     
    handle_parsing_errors=self._handle_error,
    prompt=prompt
)

Langchain工具和定制工具

工具是代理可以用来与世界交互的函数。这些工具可以是通用实用程序(例如搜索)、其他链或甚至其他代理。

范例:

  • gmail

  • google_places

  • google_search

  • google_serper

  • graphql

  • 人机交互

  • jira

  • json

如何构建我的自定义工具

我们知道GPT-3只具有直到2021年的信息,它不知道实际日期,所以我们将构建一个我们的代理可以用来知道实际日期的工具

  1. 创建自定义工具
from langchain.callbacks.manager import (
    AsyncCallbackManagerForToolRun,
    CallbackManagerForToolRun,
)
from typing import Any, Optional
from langchain.tools.base import BaseTool
from datetime import datetime

class GetTodayDate(BaseTool):
    name = "get_today_date"
    description = "you can use this tool to get today date so u can use it to calc dates before or after"
    
    def _run(
        self, query, run_manager: Optional[CallbackManagerForToolRun] = None
    ) -> str:
        """Use the tool."""
        return datetime.today().strftime('%Y-%m-%d')

    async def _arun(
        self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
    ) -> str:
        """Use the tool asynchronously."""
        raise NotImplementedError("custom_search does not support async")

如您在上面的示例中所见,该工具将允许我们的代理使用该工具来获取日期。

  1. 将其添加到代理中
from langchain.agents import initialize_agent

tools = [GetTodayDate()]

# initialize agent with tools
agent = initialize_agent(
    agent='chat-conversational-react-description',
    tools=tools,
    llm=llm,
    verbose=True,
    max_iterations=3,
    early_stopping_method='generate',
    memory=conversational_memory
)

现在,当您询问它关于实际日期或与了解今天的实际日期相关的问题时,它可以调用它。

另一个与真实使用案例相关的例子,当我们让代理处理应用程序API时,我们需要使用时间戳与我们的筛选器一起发送请求,如果用户说上个月这样的话,那么代理将需要获取今天的日期,然后知道这个日期是上个月。 没有这个工具,我们会获得2021年的上个月。

构建聊天机器人

我们的聊天机器人能够通过应用程序API访问用户数据,分析这些数据并回答用户的问题。

首先出现在我们脑海中的是使用Planner Agent

这是一个代理,它获取API YAML文件并将所有端点读取并转换为代理可以使用的工具,然后制定一个计划,其中包含需要调用的所有API以提供最佳问题回答的人,然后调用这些API来分析这些数据,然后为用户提供最佳答案。

这种方法的局限性是:

  1. 响应时间太长,因为它需要调用3或4个端点来回答问题。

  2. 在用户提出正常问题时,与用户进行友好交谈。

  3. 无法为它传递自定义工具。

  4. 无法为它传递内存。

所以我们的解决方案是构建一个自定义计划员(为原始计划员改进),以便我们可以为它传递工具并添加内存。

def create_custom_planner(
    api_spec: ReducedOpenAPISpec,
    requests_wrapper: RequestsWrapper,
    llm: BaseLanguageModel,
    shared_memory: Optional[ReadOnlySharedMemory] = None,
    memory: Optional[Any] = None,
    callback_manager: Optional[BaseCallbackManager] = None,
    verbose: bool = True,
    agent_executor_kwargs: Optional[Dict[str, Any]] = None,
    tools: Optional[List] = [],
    **kwargs: Dict[str, Any],
) -> AgentExecutor:

    tools = [
        _create_api_planner_tool(api_spec, llm),
        _create_api_controller_tool(api_spec, requests_wrapper, llm),
        *tools,
    ]
    
    prompt = PromptTemplate(
        template=API_ORCHESTRATOR_PROMPT,
        input_variables=["input", "chat_history", "agent_scratchpad"],
        partial_variables={
            "tool_names": ", ".join([tool.name for tool in tools]),
            "tool_descriptions": "\n".join(
                [f"{tool.name}: {tool.description}" for tool in tools]
            ),
        },
    )
    
     agent = ZeroShotAgent(
        llm_chain=LLMChain(llm=llm, prompt=prompt, memory=memory),
        allowed_tools=[tool.name for tool in tools],
        **kwargs,
    )

    return AgentExecutor.from_agent_and_tools(
        agent=agent,
        tools=tools,
        callback_manager=callback_manager,
        verbose=verbose,
        memory=memory,
        **(agent_executor_kwargs or {}),
    )

在我们创建自定义计划员后,我们现在遇到了另一个问题。 我们需要一个聊天代理与计划员代理一起使用,以便它们可以为用户提供友好的聊天。 如果他们询问分析他们的网店数据,将转到计划员;如果是其他正常问题,聊天代理将响应。

现在你有两种方法可以做到这一点:

  1. 计划代理将聊天用作工具。

  2. 聊天代理使用计划员作为工具。

根据我们对这两种选项的分析,最佳方法是使用聊天代理并将计划程序用作工具,但正如我们之前讨论的那样,计划程序需要时间来决定哪些API可以使用,然后调用它们3或4个端点

所以现在我们有另外两种选择:

  1. 编辑我们的自定义计划员提示,以限制他的请求数量到一次或两次调用

  2. 将所有端点构建为工具,并让聊天代理决定使用哪一个这些工具。

所以我们决定利用将计划员代理用作工具的优势,并编辑其提示模板以改进其根据应用程序API规划和分析用户输入的方式。

我们的聊天机器人的最终代码:

import aiohttp
from dotenv import dotenv_values
import langchain
from langchain.agents.agent_toolkits.openapi.spec import reduce_openapi_spec
from langchain.requests import RequestsWrapper
from langchain.chat_models import ChatOpenAI
from langchain.agents import initialize_agent, Tool, AgentType
from langchain.agents.agent_toolkits.openapi import planner
# import json
from pathlib import Path
from pprint import pprint
import yaml
from langchain import LLMMathChain
from pydantic import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser
from langchain.memory import (
  ConversationBufferMemory, 
  ReadOnlySharedMemory, 
  ConversationSummaryMemory, 
  ConversationBufferWindowMemory, 
  ConversationSummaryBufferMemory, 
  ConversationEntityMemory,
  ReadOnlySharedMemory
)
from langchain.chains import ConversationChain
# from langchain.chains.conversation.memory import ConversationBufferWindowMemory
from langchain.llms import OpenAI
from langchain.chains.conversation.prompt import ENTITY_MEMORY_CONVERSATION_TEMPLATE
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)
from langchain.memory.chat_message_histories.in_memory import ChatMessageHistory
from langchain.schema.language_model import BaseLanguageModel
from typing import Any, Callable, Dict, List, Optional
from langchain.agents.conversational_chat.base import ConversationalChatAgent
from langchain.agents.agent import AgentExecutor
from langchain.prompts.prompt import PromptTemplate
from langchain.agents import ZeroShotAgent
from langchain.chains.llm import LLMChain
from langchain.agents.agent_toolkits.openapi import planner
from custom_planner import create_openapi_agent

config = dotenv_values(".env")
print(langchain.__version__)


class CalculatorInput(BaseModel):
    question: str = Field()

class ChatBot:
    api_url = ""
    login_access_token = ""
    with open("part_doc.yml") as f:
        api_data = yaml.load(f, Loader=yaml.Loader)

    def __init__(self, email, password):
        self.email =  email
        self.password = password

    async def login(self):
        login_data = {
            "email": self.email,
            "password": self.password
        }
        async with aiohttp.ClientSession() as session:
            async with session.post(self.api_url+"/auth/token", data=login_data) as response:
                response_data =  await response.json()
                self.login_access_token = f'Bearer {response_data["access"]}'
                
    def _handle_error(error) -> str:
        return str(error)[:50]

    
    def ask_api_questions(self, question):
        llm = ChatOpenAI(openai_api_key=config.get('OPENAI_API_KEY'), temperature=0.0, model="gpt-4")
        openai_api_spec = reduce_openapi_spec(self.api_data)
        headers = {
            "Authorization": self.login_access_token,
            "Content-Type": "application/json"
        }
        requests_wrapper = RequestsWrapper(headers=headers)
        
        
        messages = [
            HumanMessage(content="Hey I am mohammed"),
            AIMessage(content="Hey mohammed, how can I help u?"),
        ]
        tools=[]
        llm_math_chain = LLMMathChain(llm=llm, verbose=True)

        
        tools.append(
            Tool.from_function(
                func=llm_math_chain.run,
                name="Calculator",
                description="useful for when you need to answer questions about math",
                args_schema=CalculatorInput
                # coroutine= ... <- you can specify an async method if desired as well
            )
        )
        
        def _create_planner_tool(llm, shared_memory):
            
            def _create_planner_agent(question: str):
                agent = create_openapi_agent(
                    openai_api_spec, 
                    requests_wrapper, 
                    llm, 
                    handle_parsing_errors=self._handle_error,
                    shared_memory=shared_memory,
                )
                return agent.run(input=question)
            
            
            return Tool(
                name="api_planner_controller",
                func=_create_planner_agent,
                description="Can be used to execute a plan of API calls and adjust the API call to retrieve the correct data for Kickbite",
            )
        
        prefix = """
            You are an AI assistant developed by xxx.
            
        """
        
        suffix = """Begin!"

        {chat_history}
        Question: {input}
        {agent_scratchpad}"""
        
        
        prompt = ZeroShotAgent.create_prompt(
           tools, 
            prefix=prefix, 
            suffix=suffix, 
            input_variables=["input", "chat_history", "agent_scratchpad"]
        )

        chat_history = ChatMessageHistory(messages=messages)
        window_memory = ConversationSummaryBufferMemory(llm=llm, chat_memory=chat_history, input_key="input", memory_key="chat_history")
        shared_memory = ReadOnlySharedMemory(memory=window_memory)
        tools.append(_create_planner_tool(llm, shared_memory))
        
        llm_chain = LLMChain(llm=llm, prompt=prompt, memory=window_memory)

        agent = ZeroShotAgent(
            llm_chain=llm_chain, 
            tools=tools, 
            verbose=True,     
            handle_parsing_errors="Check your output and make sure it conforms!",
            prompt=prompt
        )
        
        agent_executor = AgentExecutor.from_agent_and_tools(
            agent=agent, 
            tools=tools, 
            memory=window_memory
        )
        
        agent_executor.verbose = True

        output = agent_executor.run(input=question)
        print("LOL! 🦜🔗 ")
        pprint(output)

结论

希望我通过这篇文章分享的经验能让您更轻松地深入研究LangChain。本文涵盖了如何基于自己的数据集构建问答聊天机器人,以及如何优化这些聊天机器人的内存,以便您可以挑选/总结谈话并在提示中发送,而不是作为提示的一部分发送所有以前的聊天历史记录。

Dash公司网站

AI奇想空间
AI奇想空间
https://aimazing.site
AI惊奇站是一个汇聚人工智能工具、资源和教程的导航网站。 在这里,你可以发现最新的AI技术、工具和应用,学习如何使用各种AI平台和框架,获取丰富的AI资源。 欢迎广大AI爱好者加入我们的社区,开启你的AI之旅!
AI交流群
Copyright © 2024 AI奇想空间.微信