每天的專案會同步到 GitLab 上,可以前往 GitLab 查看。
有興趣的朋友歡迎留言 or 來信討論,我的信箱是 nickchen1998@gmail.com。
一般來說,要讓語言模型呈現類似記憶力的效果,其實就是把過往的對話紀錄在提問的時候一並傳遞給模型。這樣模型就可以根據過往的對話內容來回答問題。
可以看到昨天的文章當中,我們在調用語言模型回答時,傳遞了一個 list 型態的參數,裡面有 SystemMessage 以及 HumanMessage,我們可以透過查詢資料庫,
把過往的對話紀錄放置於這個地方,並在 list 的尾巴街上我們想問的問題,來達到傳遞對話紀錄的效果。
下面我們先利用 SQAlchemy 來建立一個簡單的資料庫,並將對話紀錄存入資料庫中,通常我們在設計對話紀錄的資料庫時,會在資料表當中設計一個欄位,
用來把所有的對話紀錄分組,也方便進行查詢,這邊我是透過一個叫做 nanoid 的套件來進行,針對 SQLAlchemy 比較不熟悉的朋友可以參考 這個網址。
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker
from nanoid import generate
# 定義模型
Base = declarative_base()
class ChatLog(Base):
__tablename__ = 'chat_log'
id = Column(Integer, primary_key=True, autoincrement=True)
nano_id = Column(String)
question = Column(String)
answer = Column(Integer)
if __name__ == '__main__':
engine = create_engine('sqlite:///example.db', echo=True)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
session.add(ChatLog(
nano_id=generate(),
question="我是 nick,我是一名軟體工程師。",
answer="收到,你好,我是 ChatGPT。"
))
session.commit()
接著我們就來一步一步把對話紀錄丟給模型並進行問答,首先我們先匯入必要的套件:
from env_settings import EnvSettings, BASE_DIR
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from sqlalchemy import create_engine, select
from sqlalchemy.orm import sessionmaker
from model import ChatLog
接著我們要透過查詢資料庫把對話紀錄取出來:
env_settings = EnvSettings()
engine = create_engine(f"sqlite:///{BASE_DIR / 'example.db'}", echo=True)
session = sessionmaker(bind=engine)()
stmt = select(ChatLog).where(ChatLog.nano_id == "U26Wn16LS6aRrx6tUw8Ak")
chat_logs = session.execute(stmt).scalars().all()
再來我們要把對話紀錄轉換成 LangChain 的訊息格式,並在串列的尾巴使用 HumanMessage 放上我們想提問的問題:
messages = [SystemMessage(content="你是一個繁體中文機器人,會理解使用者的提問並使用繁體中文回答。")]
for chat_log in chat_logs:
messages.append(HumanMessage(content=chat_log.question))
messages.append(AIMessage(content=chat_log.answer))
messages.append(HumanMessage(content="你好,請問我是誰?"))
最後就可以使用 invoke 來調用模型進行問答並查看結果:
ai_message = llm.invoke(messages)
print(ai_message.content)
可以看到下圖中的執行結果,GPT 有成功識別出我們是誰,並給予正確的資訊:
剛剛我們採取了手動調閱對話紀錄的方式,將一批對話紀錄傳遞給語言模型,這個方式的優點是,會保留最原始的對話紀錄,但通常語言模型會有一些 token 的
字數限制等,如果今天想要越過這個限制,或是想要加速一些效能的話,我們可以試著將對話紀錄做摘要,只保留對話紀錄的重點,這樣可以減少傳遞給語言模型的資料量。
而在 LangChain 當中,也幫我們封裝好了一個物件叫做 SQLChatMessageHistory
讓我們可以更快速的進行這項工作,不需要從設定資料表開始,當然如果
你需要更加客製化的製作 prompt 或是用了一寫比較稀有的資料庫等等的特殊情況,你也可以採用手動調閱 or 覆寫這個物件來進行。
讓我們快速看一下範例:
可以看到寫了一個 get_session_history 的方法,這個方法會回傳一個 SQLChatMessageHistory 物件,這個物件會幫我們把對話紀錄存入資料庫中,
我們也可以在這裡設定資料庫的連線字串等等,並不一定要使用 sqlite。
from env_settings import EnvSettings, BASE_DIR
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
def get_session_history(session_id):
return SQLChatMessageHistory(
session_id,
f"sqlite:///{BASE_DIR / 'example.db'}"
)
env_settings = EnvSettings()
llm = ChatOpenAI(
api_key=env_settings.OPENAI_API_KEY,
model_name="gpt-4o"
)
runner = RunnableWithMessageHistory(
llm,
get_session_history
)
ai_message = runner.invoke(
[HumanMessage(content="你好,我是 nick")],
config={"configurable": {"session_id": "1"}},
)
print(ai_message.content)
下圖是執行完成的結果,可以看到 langchain 協助我們在資料庫當中自己生出了一張表,並且依照我們給予的 session_id 建立了兩筆紀錄,
分別是問題以及回答。
今天我們介紹了如何讓語言模型擁有記憶力,明天我們要來介紹在 langchain 當中,該如何下 prompt,幫助我們更加彈性的進行問答內容的編輯。