iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
生成式 AI

LLM與生成式AI筆記系列 第 16

Day 16: langchain 由入門到熟練(建構一個Chatbot-使用Open AI -API)

  • 分享至 

  • xImage
  •  

前言:

原本我是想使用 Huggingface 的 api 串接來實現這個教學的,不過因為我在東搞西搞之後發現要 huggingface pro 的訂閱才能使用 huggingface 直接調用模型 的遠端API ,想了想我還是先用我Open AI -API 帳號裡面還有剩一點的錢完成這個教學就好了。

建構一個 Chatbot:

此範例是根據這個改編

我們將透過一個範例來說明如何設計和實現由LLM支援的Chatbot。該Chatbot將能夠進行對話並記住先前的互動。

請注意,我們建立的這個Chatbot將僅使用語言模型進行對話。您可能正在尋找其他幾個相關概念:

  • 會話式 RAG:透過外部資料來源實現Chatbot體驗。

  • Agent:建構一個可以採取行動的Chatbot。

本教程將涵蓋對這兩個更高級主題有幫助的基礎知識,但如果您選擇,可以直接跳到那裡。

!pip install langchain
!pip install -qU langchain-openai
Requirement already satisfied: langchain in /usr/local/lib/python3.10/dist-packages (0.2.14)
Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.2)
Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.32)
Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.10.2)
Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)
Requirement already satisfied: langchain-core<0.3.0,>=0.2.32 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.2.33)
Requirement already satisfied: langchain-text-splitters<0.3.0,>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.2.2)
Requirement already satisfied: langsmith<0.2.0,>=0.1.17 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.1.99)
Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.26.4)
Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.8.2)
Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.32.3)
Requirement already satisfied: tenacity!=8.4.0,<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (8.5.0)
Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (2.3.5)
Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)
Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (24.2.0)
Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)
Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.5)
Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)
Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.32->langchain) (1.33)
Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.32->langchain) (24.1)
Requirement already satisfied: typing-extensions>=4.7 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.32->langchain) (4.12.2)
Requirement already satisfied: orjson<4.0.0,>=3.9.14 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.17->langchain) (3.10.7)
Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (0.7.0)
Requirement already satisfied: pydantic-core==2.20.1 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (2.20.1)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2024.7.4)
Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)
Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.10/dist-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.3.0,>=0.2.32->langchain) (3.0.0)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.0/52.0 kB 1.1 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 362.4/362.4 kB 4.4 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.1/1.1 MB 17.3 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 75.6/75.6 kB 4.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 77.9/77.9 kB 4.3 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 318.9/318.9 kB 17.6 MB/s eta 0:00:00
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 58.3/58.3 kB 3.3 MB/s eta 0:00:00
[?25h
import getpass
import os
from langchain_openai import ChatOpenAI

os.environ["LANGCHAIN_TRACING_V2"] = "true"
# 替換為你的LANGCHAIN_API_KEY
os.environ["LANGCHAIN_API_KEY"] = "替換為你的LANGCHAIN_API_KEY"

os.environ["OPENAI_API_KEY"] = "替換為你的OPENAI_API_KEY"


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

model.invoke([HumanMessage(content="你好 我是飛魚")])

AIMessage(content='你好飛魚,很高興能和你聊天。有什麼可以幫助你的嗎?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 37, 'prompt_tokens': 17, 'total_tokens': 54}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-971d220b-7ed4-4c25-93c6-7203501457d1-0', usage_metadata={'input_tokens': 17, 'output_tokens': 37, 'total_tokens': 54})

API 參考:HumanMessage
該模型本身沒有任何狀態概念。例如,如果您提出後續問題:
model.invoke([HumanMessage(content="叫我的名字?")])

model.invoke([HumanMessage(content="我叫啥?")])
AIMessage(content='抱歉,我无法知道你的名字,因为我是一个虚拟助手。您可以告诉我您的名字,我们可以继续交流。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 48, 'prompt_tokens': 13, 'total_tokens': 61}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-7170be91-6477-4c0a-8d55-729b6ce88943-0', usage_metadata={'input_tokens': 13, 'output_tokens': 48, 'total_tokens': 61})

可以看出,它沒有將前面的對話轉入上下文,無法回答問題。這會帶來糟糕的聊天機器人體驗!

為了解決這個問題,我們需要將整個對話歷史記錄傳遞到模型中。讓我們看看這樣做時會發生什麼:

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. How can I assist you today, Bob?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 35, 'total_tokens': 49}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-8c5dca86-adaf-4bc9-8b62-1990c682f667-0', usage_metadata={'input_tokens': 35, 'output_tokens': 14, 'total_tokens': 49})

上方為官方的教學,現在我們可以看到我們從GPT那得到還可以的回應!

這就是聊天機器人對話互動能力的基本想法。

不過假如改成中文會有怎樣的效果,效果如下:

from langchain_core.messages import AIMessage

model.invoke(
    [
        HumanMessage(content="你好 我是飛魚"),
        AIMessage(content="你好飛魚,很高興能和你聊天。有什麼可以幫助你的嗎?"),
        HumanMessage(content="我的名字是?"),
    ]
)
AIMessage(content='抱歉,我不知道你的名字。你可以告訴我你的名字嗎?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 28, 'prompt_tokens': 67, 'total_tokens': 95}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-f734f6d1-ad04-4ffa-8b2a-82365cd96580-0', usage_metadata={'input_tokens': 67, 'output_tokens': 28, 'total_tokens': 95})
from langchain_core.messages import AIMessage

model.invoke(
    [
        HumanMessage(content="你好 我的名字叫飛魚"),
        AIMessage(content="你好飛魚,很高興能和你聊天。有什麼可以幫助你的嗎?"),
        HumanMessage(content="我的名字是?"),
    ]
)
AIMessage(content='您剛才說您的名字叫飛魚。您想要我們用另一個名字來稱呼您嗎?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 45, 'prompt_tokens': 71, 'total_tokens': 116}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-a417d8a3-9f3c-4133-9da7-601ebef41f7b-0', usage_metadata={'input_tokens': 71, 'output_tokens': 45, 'total_tokens': 116})

從上面可以看到,在繁體中文方面可能因為沒有那麼多的資料,又或者因為沒有那麼多的微調或RLHF,所以必須寫得特別直接才有正確的回應,不過這只是GPT 3.5的情況,或許這會在GPT4或之後的版本就改善了。

歷史訊息

我們可以使用訊息歷史記錄類別來包裝我們的模型並使其具有狀態。這可用於追蹤模型的輸入和輸出,並將它們儲存在某個資料儲存中。未來的互動將載入這些訊息並將它們作為輸入的一部分傳遞到運作中。讓我們看看如何使用它!

首先,讓我們確保安裝langchain-community,因為我們將使用其中的整合來儲存訊息歷史記錄。

! pip install langchain_community
Collecting langchain_community
  Downloading langchain_community-0.2.12-py3-none-any.whl.metadata (2.7 kB)
Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (6.0.2)
Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (2.0.32)
Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (3.10.2)
Collecting dataclasses-json<0.7,>=0.5.7 (from langchain_community)
  Downloading dataclasses_json-0.6.7-py3-none-any.whl.metadata (25 kB)
Requirement already satisfied: langchain<0.3.0,>=0.2.13 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (0.2.14)
Requirement already satisfied: langchain-core<0.3.0,>=0.2.30 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (0.2.33)
Requirement already satisfied: langsmith<0.2.0,>=0.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (0.1.99)
Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (1.26.4)
Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (2.32.3)
Requirement already satisfied: tenacity!=8.4.0,<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain_community) (8.5.0)
Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain_community) (2.3.5)
Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain_community) (1.3.1)
Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain_community) (24.2.0)
Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain_community) (1.4.1)
Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain_community) (6.0.5)
Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain_community) (1.9.4)
Requirement already satisfied: async-timeout<5.0,>=4.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain_community) (4.0.3)
Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json<0.7,>=0.5.7->langchain_community)
  Downloading marshmallow-3.21.3-py3-none-any.whl.metadata (7.1 kB)
Collecting typing-inspect<1,>=0.4.0 (from dataclasses-json<0.7,>=0.5.7->langchain_community)
  Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)
Requirement already satisfied: langchain-text-splitters<0.3.0,>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from langchain<0.3.0,>=0.2.13->langchain_community) (0.2.2)
Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain<0.3.0,>=0.2.13->langchain_community) (2.8.2)
Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.30->langchain_community) (1.33)
Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.30->langchain_community) (24.1)
Requirement already satisfied: typing-extensions>=4.7 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.30->langchain_community) (4.12.2)
Requirement already satisfied: orjson<4.0.0,>=3.9.14 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.0->langchain_community) (3.10.7)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain_community) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain_community) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain_community) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain_community) (2024.7.4)
Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain_community) (3.0.3)
Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.10/dist-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.3.0,>=0.2.30->langchain_community) (3.0.0)
Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain<0.3.0,>=0.2.13->langchain_community) (0.7.0)
Requirement already satisfied: pydantic-core==2.20.1 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain<0.3.0,>=0.2.13->langchain_community) (2.20.1)
Collecting mypy-extensions>=0.3.0 (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain_community)
  Downloading mypy_extensions-1.0.0-py3-none-any.whl.metadata (1.1 kB)
Downloading langchain_community-0.2.12-py3-none-any.whl (2.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.3/2.3 MB 12.1 MB/s eta 0:00:00
[?25hDownloading dataclasses_json-0.6.7-py3-none-any.whl (28 kB)
Downloading marshmallow-3.21.3-py3-none-any.whl (49 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.2/49.2 kB 2.7 MB/s eta 0:00:00
[?25hDownloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)
Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)
Installing collected packages: mypy-extensions, marshmallow, typing-inspect, dataclasses-json, langchain_community
Successfully installed dataclasses-json-0.6.7 langchain_community-0.2.12 marshmallow-3.21.3 mypy-extensions-1.0.0 typing-inspect-0.9.0
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)

API 參考:BaseChatMessageHistory |記憶體中聊天訊息歷史記錄| RunnableWithMessageHistory

我們現在需要建立一個 config 每次傳遞到可運行物件中的物件。此配置包含的資訊不是直接輸入的一部分,但仍然有用。在本例子中,我們需要包含一個 session_id .這應該看起來像:

config = {"configurable": {"session_id": "abc1"}}
response = with_message_history.invoke(
    [HumanMessage(content="Hi! I'm Bob")],
    config=config,
)
response = with_message_history.invoke(
    [HumanMessage(content="What's my name?")],
    config=config,
)

response.content

'Your name is Bob. How can I help you today, Bob?'
config = {"configurable": {"session_id": "abc2"}}
response = with_message_history.invoke(
    [HumanMessage(content="你好 我的名字是取把勾")],
    config=config,
)
response = with_message_history.invoke(
    [HumanMessage(content="我的名字是? ")],
    config=config,
)

response.content
'非常抱歉,我无法得知您的真实姓名。如果您有其他问题或需要帮助,请随时告诉我。我会尽力提供支持。'
model = ChatOpenAI(model="gpt-4o")
config = {"configurable": {"session_id": "abc3"}}
response = with_message_history.invoke(
    [HumanMessage(content="你好 我的名字是取把勾")],
    config=config,
)
response = with_message_history.invoke(
    [HumanMessage(content="我的名字是? ")],
    config=config,
)

response.content

'抱歉,我无法直接获取你的名字。请问有什么问题或者需要帮助的吗?'

看來這方面就算改成 gpt-4o的模型也沒有改變。

model = ChatOpenAI(model="gpt-3.5-turbo")
config = {"configurable": {"session_id": "abc3"}}

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

response.content
"I'm sorry, I don't have access to personal information like your name. How can I assist you today?"

然而,我們是可以回到原來的對話(因為其對話保存在資料庫中)

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

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

response.content
'Your name is Bob. How can I assist you, Bob?'
config = {"configurable": {"session_id": "abc2"}}

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

response.content
"I'm sorry, I don't have access to personal information such as your name. If you would like to share your name with me, feel free to do so. How can I assist you today?"

如此一來,我們便能讓聊天機器人同時與多位用戶暢談!(看來只能用英文)

目前,我們只是在模型周圍添加了一個簡單的持久層(存儲對話)。接下來,我們可以引入提示模板,使對話變得更加複雜和個性化。

提示模板

提示模板有助於將原始用戶資訊轉換為LLM可處理的格式。在當前情境中,原始用戶輸入僅僅是一條訊息,我們將其直接傳遞給LLM。現在,讓我們稍微複雜化一點。首先,讓我們添加一條包含自定義指令的系統訊息(但仍將訊息作為輸入)。接下來,我們將添加更多輸入,而不僅僅是訊息。

首先,讓我們添加一條系統訊息。為此,我們將創建一個 ChatPromptTemplate。我們將利用 MessagesPlaceholder 來傳遞所有訊息。

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
'Hello Bob! How can I assist you today?'
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="你好! 我是飛魚")]})

response.content
'你好! 有什麼問題我可以幫忙解答嗎?'

API 參考:ChatPromptTemplate | MessagesPlaceholder

請注意,這稍微改變了輸入類型 - 現在我們不再傳入一個訊息列表,而是傳入一個字典,其中包含一個 messages 鍵,該鍵的值是一個訊息列表。

現在,我們可以像之前一樣,將其封裝在 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
'Hello Jim! How can I assist you today?'
response = with_message_history.invoke(
    [HumanMessage(content="What's my name?")],
    config=config,
)

response.content
'Your name is Jim.'

不過繁中方面感覺還是沒微調過

model = ChatOpenAI(model="gpt-3.5-turbo")
with_message_history = RunnableWithMessageHistory(chain, get_session_history)
config = {"configurable": {"session_id": "abc6"}}
response = with_message_history.invoke(
    [HumanMessage(content="你好!我是阿拉丁")],
    config=config,
)

response.content
'你好,阿拉丁!有什么可以帮助你的吗?'
response = with_message_history.invoke(
    [HumanMessage(content="你猜猜我是?")],
    config=config,
)

response.content
'抱歉,我无法猜测你是谁。您可以告诉我更多关于您的信息吗?我会尽力帮助您。'

現在讓我們來讓提示模板變得更為複雜一點。假設提示模板現在看起來像這樣:

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": "Spanish"}
)

response.content
'¡Hola, Bob! ¿En qué puedo ayudarte hoy?'

請注意,我們在提示中新增了一個 language 輸入。現在,我們可以調用 chain並傳入我們選擇的語言。

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="你好!我是馬克")], "language": "Spanish"}
)

response.content
'¡Hola Mark! ¿En qué puedo ayudarte hoy?'

看來這個有微調過,或是有相關的資料集訓練過。

讓我們將這個更複雜的鏈封裝在 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": "Spanish"},
    config=config,
)

response.content
'¡Hola Todd! ¿En qué puedo ayudarte hoy?'
response = with_message_history.invoke(
    {"messages": [HumanMessage(content="whats my name?")], "language": "Spanish"},
    config=config,
)

response.content
'Tu nombre es Todd. ¿Hay algo más en lo que pueda ayudarte?'
with_message_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="messages",
)
config = {"configurable": {"session_id": "abc12"}}
response = with_message_history.invoke(
    {"messages": [HumanMessage(content="你好!我是阿桃")], "language": "Spanish"},
    config=config,
)

response.content
'¡Hola! ¿En qué puedo ayudarte hoy?'
response = with_message_history.invoke(
    {"messages": [HumanMessage(content="我叫啥?")], "language": "Spanish"},
    config=config,
)

response.content
'Tu nombre es "阿桃"。 ¿Hay algo más en lo que pueda ayudarte?'

這個中文是回答正確的,很神奇就是,可能是封裝的類裡面有設定相關的prompt,或者之前的系統prompt 有產生作用。

管理對話歷史

在構建聊天機器人時,一個重要的概念是要理解如何管理對話歷史。如果不加以管理,訊息列表會無限增長,可能超出 LLM 的上下文窗口。因此,添加一個步驟來限制傳入訊息的大小非常重要。

重要的是,您需要在提示模板之前執行此操作,但在從訊息歷史記錄載入先前的訊息之後執行。

我們可以在提示前添加一個簡單的步驟,以適當地修改 messages 鍵,然後將這個新的鏈封裝在 Message History 類中。

LangChain 提供了一些內建的輔助工具來管理訊息列表( managing a list of messages)。在這種情況下,我們將使用 trim_messages 輔助工具來減少我們發送到模型的訊息數量。此修剪器允許我們指定要保留多少個 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)
[SystemMessage(content="you're a good assistant"),
 HumanMessage(content='whats 2 + 2'),
 AIMessage(content='4'),
 HumanMessage(content='thanks'),
 AIMessage(content='no problem!'),
 HumanMessage(content='having fun?'),
 AIMessage(content='yes!')]

API Reference:SystemMessage | trim_messages

要在我們的 chain 中使用它,我們只需要在將 messages 輸入傳遞給提示模板之前執行修剪器。

現在,如果我們嘗試詢問模型我們的名字,它將無法回答,因為我們修剪了聊天歷史記錄中的那一部分。

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
"I'm sorry, I don't have access to your name. How can I assist you today?"

API Reference: RunnablePassthrough

但如果我們詢問有關過去幾條消息中的資訊,它會記得:

response = chain.invoke(
    {
        "messages": messages + [HumanMessage(content="what math problem did i ask")],
        "language": "English",
    }
)
response.content
'You asked "what\'s 2 + 2?"'

現在,讓我們將其封裝在 Message History 中。

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
"I'm sorry, I don't know your name."

如預期的那樣,我們一開始提到名字的第一條消息已被修剪掉了。此外,聊天歷史記錄中現在多了兩條新消息(我們最新的問題和最新的回應)。這意味著之前在我們的對話歷史中可訪問的更多資訊現在不再可用!在這種情況下,我們最初的數學問題也已從歷史記錄中修剪掉,因此模型不再知道它了:

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

response.content
"You haven't asked me a math problem yet. Feel free to ask, and I'll do my best to help you with it."

串流傳輸

現在,我們已經有一個功能性的聊天機器人。然而,對於聊天機器人應用來說,一個非常重要的用戶體驗考量是串流傳輸。大型語言模型 (LLMs) 有時可能需要一段時間才能回應,因此為了改善用戶體驗,大多數應用程序會在每個 token 生成時就將其串流傳輸回來。這允許用戶看到進度。

實際上,這樣做非常簡單!

所有 chain 都公開了一個 .stream 方法,使用訊息歷史記錄的鏈也不例外。我們只需使用該方法來獲得串流回應即可。

config = {"configurable": {"session_id": "abc15"}}
for r in with_message_history.stream(
    {
        "messages": [HumanMessage(content="hi! I'm todd. tell me a joke")],
        "language": "English",
    },
    config=config,
):
    print(r.content, end="|")
|Sure|,| Todd|!| Here|'s| a| joke| for| you|:| Why| did| the| scare|crow| win| an| award|?| Because| he| was| outstanding| in| his| field|!| 😄||

後續步驟
既然您已經了解了在 LangChain 中創建聊天機器人的基礎知識,接下來您可能會對一些進階教程感興趣:

對話式 RAG: 使聊天機器人能夠與外部資料源進行交互。
代理: 構建一個可以採取行動的聊天機器人。
如果您想深入了解具體細節,以下是一些值得關注的內容:

串流傳輸: 串流傳輸對於聊天應用至關重要。
如何添加訊息歷史記錄: 深入了解與訊息歷史記錄相關的所有內容。
如何管理大型訊息歷史記錄: 更多管理大型聊天歷史記錄的技術。


這個連結為可以直接運行的colab版本


心得:

感覺 langchain 以及 Open AI 在繁中方面可能因為資料量或是客戶太少,所以可能沒有做很多的微調,之後可能實際在用的時候要下很多的prompt跟微調才能達到在英文或簡體中文方面應用的理想狀態就是了。


目錄:


Langchain:


  1. 介紹:

  2. 教學:

    1. 基礎:

      1. 使用 LCEL 建立簡單的 LLM 應用
      2. 建構一個聊天機器人
      3. 建立向量儲存和檢索器
      4. 建立 Agent
    2. 將外部資訊導入到 Agent 的運作中
      5. 建立檢索增強生成 (RAG) 應用程式
      6. 建立會話式 RAG 應用程式
      7. 基於 SQL 資料建構問答系統
      8. 建構查詢分析系統
      9. 建立本地 RAG 應用程式
      10. 透過圖形資料庫建立問答應用程式
      11. 建構 PDF 攝取和問答系統

    3. 特定的任務或功能
      12. 建構抽取資料的方法
      13. 產生合成資料
      14. 將文字用標籤分類
      15. 總結文本


LangGraph:


  1. 快速入門:

  2. 聊天機器人:

    1. 客戶支持機器人
    2. 根據使用者需求產生 prompt
    3. 程式碼助手
  3. RAG:
    4.自適應 RAG
    5.使用本地的LLM進行自適應 RAG
    6.自主檢索 RAG(Agentic RAG)
    7.自修正 RAG(Corrective RAG)
    8. 使用本地的LLM進行自修正 RAG
    9.自我詢問RAG(Self-RAG)
    10.使用本地的LLM自我詢問RAG(Self-RAG)
    11.SQL Agent

  4. Agent 架構:

    1. 多 Agent系統:
      12. 兩個Agent的協作
      13. 監督
      14. 分層團隊
      2.規劃型Agent:
      15. 規劃與執行
      16. 無觀察執行
      17. LLMCompiler
      3.反思與批評:
      18.基本反思
      19.反思
      20.語言 Agent 樹搜尋
      21.自主發現代理
  5. 評估與分析:
    22. 基於代理的評估
    23. 在LangSmith中的評估

  6. 實驗性項目:
    24. 網路搜索Agent(STORM)
    25. TNT-LLM
    26. Web導航 Agent
    27. 競賽中的程式設計
    28. 複雜資料抽取


LangSmith:


  1. 快速入門:
  2. 為您的 LLM 應用添加可觀察的方法
  3. 評估您的 LLM 應用
  4. 優化分類器
  5. RAG 評估
  6. 回測
  7. 代理商評價
  8. 優化 LangSmith 上的追蹤支出

整合 LangGraph 的工具介紹以及使用:


  1. agent-service-toolkit


上一篇
Day 15: langchain 由入門到熟練( 使用 LCEL 建立簡單的 LLM 應用-使用huggingface在colab載入模型)
下一篇
Day 17: langchain 由入門到熟練(建立向量儲存和檢索器-使用Open AI -API)
系列文
LLM與生成式AI筆記31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言