LangChain-v0.2 构建聊天机器人

本文将介绍如何设计和实现 LLM 驱动的聊天机器人的示例。该聊天机器人能够进行对话和记住历史聊天记录。

阅读本文后,你将大致了解以下内容:

1、Chat Models聊天模型;

2、使用 LangSmith 调试和跟踪应用程序;

3、Prompt Templates 提示模板;

4、Chat History 聊天记录;

5、Streaming 流;

请注意,我们构建的这个聊天机器人将仅使用语言模型进行对话。涉及一下相关概念:

1、Conversational RAG: 通过外部数据源启用聊天机器人体验;

2、Agents: 构建可以执行操作的聊天机器人;

一、Installation安装

LangChain安装:

pip install langchain

二、LangSmith

您使用 LangChain 构建的许多应用程序将包含多个步骤,其中包含多次调用 LLM 调用。
随着这些应用程序变得越来越复杂,通过LangSmith能够检查您的链或代理内部到底发生了什么。

1)注册LangSmith

访问地址:https://smith.langchain.com/

2)点击设置

创建API KEY

保存好生成的KEY,接下来在项目中需要配置这个KEY ;

3)创建Projects

4)在代码中配置

在代码中设置LangSmith KEY环境变量以开始记录跟踪:

import os
os.environ["LANGCHAIN_TRACING_V2"]="true"
os.environ["LANGCHAIN_API_KEY"]="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"

 5)在Projects中查看 链或代理内部调用

三、使用语言模型

LangChain支持许多不同的语言模型,包含:OpenAI、Anthropic、Azure、Google、Cohere、FireworksAI、Groq、MistralAI、TogetherAI等,您可以互换使用 ,选择您要使用的语言模型!

下面内容将居于OpenAI语言模型进行演示:

1)LangChain OpenAI安装:
pip install -qU langchain-openai

让我们首先直接使用模型。是LangChain的实例,它们公开了一个标准接口来与它们进行交互。只需简单地调用模型,我们就可以将消息列表传递给该方法。 

from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-3.5-turbo")
2)HumanMessage 
from langchain_core.messages import HumanMessage

model.invoke([HumanMessage(content="Hi! I'm Bob")])

 输出聊天回复:

AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 12, 'total_tokens': 22}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-92ca123c-4557-4403-8b17-6c4d74d5e1ff-0', usage_metadata={'input_tokens': 12, 'output_tokens': 10, 'total_tokens': 22})

该模型本身没有任何状态概念。例如,如果您提出后续问题:

model.invoke([HumanMessage(content="What's my name?")])

输出聊天回复:

AIMessage(content="I'm sorry, I do not have access to personal information such as your name.", response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 12, 'total_tokens': 29}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-7a6e352f-7db2-4173-a809-565a976a8768-0', usage_metadata={'input_tokens': 12, 'output_tokens': 17, 'total_tokens': 29})

我们可以看到,它并没有将之前的对话变成上下文,也无法回答上文说过的内容。这是非常差的聊天机器人体验!

为了解决这个问题,我们需要将整个对话历史记录传递到模型中。让我们看看这样做会带来什么样效果:

from langchain_core.messages import AIMessage

model.invoke(
    [
        HumanMessage(content="Hi! I'm Bob"),
        AIMessage(content="Hello Bob! How can I assist you today?"),
        HumanMessage(content="What's my name?"),
    ]
)

输出聊天回复:

AIMessage(content='Your name is Bob. Is there anything else I can help you with, Bob?', response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 35, 'total_tokens': 52}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-ced35134-5f6a-41bf-a250-8b40545bf3c2-0', usage_metadata={'input_tokens': 35, 'output_tokens': 17, 'total_tokens': 52})

现在我们可以看到我们得到了很好的回应!

上下文(历史记录)是支撑聊天机器人对话能力的基本思想。那么,我们如何最好地实现这一点呢?

四、Message History 消息历史记录

我们可以使用 Message History 类来包装我们的模型并使其有状态。这将跟踪模型的输入和输出,并将它们存储在某个数据存储中。然后,未来的交互将加载这些消息,并将它们作为输入的一部分传递到链中。

1)langchain_community安装:

pip install langchain_community

2)API使用

我们可以导入相关类并设置我们的链,该链包装模型并添加此消息历史记录。这里的一个关键部分是我们传入的函数。此函数应接受并返回 Message History 对象。这用于区分单独的会话,并且在调用新链时应作为配置的一部分传入(我们将展示如何执行此操作)。

from langchain_core.chat_history import (
    BaseChatMessageHistory,
    InMemoryChatMessageHistory,
)
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}


def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]


with_message_history = RunnableWithMessageHistory(model, get_session_history)

3)配置数据

配置历史消息缓存信息,让同一个回话具备历史聊天记录,同时不同回话的历史聊天记录数据是隔离的。

config = {"configurable": {"session_id": "abc2"}}

response = with_message_history.invoke(
    [HumanMessage(content="Hi! I'm Bob")],
    config=config,
)

response.content
response = with_message_history.invoke(
    [HumanMessage(content="What's my name?")],
    config=config,
)

response.content

聊天机器人现在已经可以记住了关于我们的历史记录。如果我们更改配置以引用不同的ID ,我们可以看到它重新开始对话。 

config = {"configurable": {"session_id": "abc3"}}

response = with_message_history.invoke(
    [HumanMessage(content="What's my name?")],
    config=config,
)

response.content

同时,我们也可以回到原始对话(因为我们将其保存在数据库中) 

config = {"configurable": {"session_id": "abc2"}}

response = with_message_history.invoke(
    [HumanMessage(content="What's my name?")],
    config=config,
)

response.content

这样我们就实现了如何支持聊天机器人与许多用户进行对话的方式! 

现在,我们所做的只是在模型周围添加一个简单的持久层。我们可以通过添加提示模板开始使内容更加复杂和个性化。

五、Prompt templates 提示模板

提示模板有助于将原始用户信息转换为 LLM 可以使用的格式。在这种情况下,原始用户输入只是一条消息,我们将它传递给 LLM。现在让我们让它更复杂一点。首先,让我们添加一个带有一些自定义指令的系统消息(但仍然将消息作为输入)。接下来,除了消息之外,我们还将添加更多输入。

1)首先,让我们添加一条系统消息。为此,我们将创建一个 ChatPromptTemplate。我们将利用所有消息传递。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a helpful assistant. Answer all questions to the best of your ability.",
        ),
        MessagesPlaceholder(variable_name="messages"),
    ]
)

chain = prompt | model

请注意,这略微改变了输入类型 - 我们现在不是传入消息列表,而是传入一个带有键的字典,其中包含消息列表。

response = chain.invoke({"messages": [HumanMessage(content="hi! I'm bob")]})

response.content

2)现在,我们可以将其包装在与以前相同的 Messages History 对象中

with_message_history = RunnableWithMessageHistory(chain, get_session_history)

config = {"configurable": {"session_id": "abc5"}}

response = with_message_history.invoke(
    [HumanMessage(content="Hi! I'm Jim")],
    config=config,
)

response.content
response = with_message_history.invoke(
    [HumanMessage(content="What's my name?")],
    config=config,
)

response.content

3)修改提示稍微复杂一点,让我们的提示模板看起来像这样: 

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a helpful assistant. Answer all questions to the best of your ability in {language}.",
        ),
        MessagesPlaceholder(variable_name="messages"),
    ]
)

chain = prompt | model

请注意,我们已向提示添加了新输入。现在,我们可以调用链并传递我们选择的语言。

response = chain.invoke(
    {"messages": [HumanMessage(content="hi! I'm bob")], "language": "chinese"}
)

response.content

4)现在,让我们将这个更复杂的链包装在 Message History 类中。这一次,由于输入中有多个键,我们需要指定用于保存聊天记录的正确键。

with_message_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="messages",
)

config = {"configurable": {"session_id": "abc11"}}

response = with_message_history.invoke(
    {"messages": [HumanMessage(content="hi! I'm todd")], "language": "chinese"},
    config=config,
)

response.content
response = with_message_history.invoke(
    {"messages": [HumanMessage(content="whats my name?")], "language": "chinese"},
    config=config,
)

response.content

六、Managing Conversation History 管理对话历史记录

构建聊天机器人时要了解的一个重要概念 -- 如何管理对话历史记录。如果不进行管理,消息列表将无限制增长,并可能溢出 LLM 的上下文窗口。因此,添加一个步骤来限制您传入的消息的大小非常重要。

为此,我们可以在提示符前面添加一个简单的步骤来适当地修改记录(token),然后将该新链包装在 Message History 类中。

1)LangChain带有一些内置的帮助程序,用于管理消息列表。在本例中,我们将使用 trim_messages 帮助程序来减少发送到模型的消息数。trimmer 允许我们指定要保留的token数量,以及其他参数,例如我们是否要始终保留系统消息以及是否允许部分消息:

from langchain_core.messages import SystemMessage, trim_messages

trimmer = trim_messages(
    max_tokens=65,
    strategy="last",
    token_counter=model,
    include_system=True,
    allow_partial=False,
    start_on="human",
)

messages = [
    SystemMessage(content="you're a good assistant"),
    HumanMessage(content="hi! I'm bob"),
    AIMessage(content="hi!"),
    HumanMessage(content="I like vanilla ice cream"),
    AIMessage(content="nice"),
    HumanMessage(content="whats 2 + 2"),
    AIMessage(content="4"),
    HumanMessage(content="thanks"),
    AIMessage(content="no problem!"),
    HumanMessage(content="having fun?"),
    AIMessage(content="yes!"),
]

trimmer.invoke(messages)

max_tokens=65, include_system=True, 明显输出的结果已经按配置要求,删除超过65数量的最早的聊天记录,并保留了设置的系统记录。

2)要在我们的链中使用它,我们只需要先运行trimmer,然后将trimmer输入传递到提示符 。

现在,如果我们尝试向模型询问我的名字,它不会知道,因为我的名字的聊天记录,因为超出65,而被删除掉了:

from operator import itemgetter

from langchain_core.runnables import RunnablePassthrough

chain = (
    RunnablePassthrough.assign(messages=itemgetter("messages") | trimmer)
    | prompt
    | model
)

response = chain.invoke(
    {
        "messages": messages + [HumanMessage(content="what's my name?")],
        "language": "English",
    }
)
response.content

但是,如果我们询问的是最后几条消息,它是会记住: 

response = chain.invoke(
    {
        "messages": messages + [HumanMessage(content="what math problem did i ask")],
        "language": "English",
    }
)
response.content

3)现在让我们将其包装在消息历史记录中:

with_message_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="messages",
)

config = {"configurable": {"session_id": "abc20"}}

response = with_message_history.invoke(
    {
        "messages": messages + [HumanMessage(content="whats my name?")],
        "language": "English",
    },
    config=config,
)

response.content

 不出所料,我们说出我们名字的第一条消息已被删除。此外,聊天记录中现在还有两条新消息(我们的最新问题和最新回复)。这意味着过去在我们的对话历史记录中可以访问的更多信息,现在已经不再可用(被删除)!在本例中,我们最初的数学问题也从历史记录中删减了,因此模型不再知道它:

response = with_message_history.invoke(
    {
        "messages": [HumanMessage(content="what math problem did i ask?")],
        "language": "English",
    },
    config=config,
)

response.content

七、Streaming 流

现在我们有了一个功能聊天机器人。然而,聊天机器人应用程序的一个非常重要的用户体验,就是要考虑流媒体因素。LLM 有时可能需要一段时间才能做出响应,因此为了改善用户体验,大多数应用程序所做的一件事就是在生成每个token时流回每个token。这允许用户查看进度。

在Langchain 这其实非常容易做到,所有链都公开一个方法,使用消息历史记录的链也不例外。我们可以简单地使用该方法来返回流式响应。

config = {"configurable": {"session_id": "abc15"}}
for r in with_message_history.stream(
    {
        "messages": [HumanMessage(content="hi! I'm todd. tell me a joke")],
        "language": "chinese",
    },
    config=config,
):
    print(r.content, end="|")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sziitjin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值