Große Sprachmodelle (LLM) können zuverlässigere Antworten liefern, wenn sie mit abgerufenen Kontexten aus einer Wissensdatenbank ergänzt werden, was als Retrieval Augmented Generation (RAG) bekannt ist. In unseren früheren Blogs haben wir die Leistungssteigerung (opens new window) und die Machbarkeit in Bezug auf Kosten und Latenz (opens new window) von RAG diskutiert. In diesem Blog stellen wir Ihnen eine fortgeschrittene Verwendung der RAG-Pipeline vor: den Aufbau eines Chatbots mit Retrieval Augmented Generation mit MyScale. Sie können es auch auf unserem HuggingFace Space (opens new window) ausprobieren.
Hinweis:
Besuchen Sie den HuggingFace Space von MyScale (opens new window), um unseren Chatbot auszuprobieren.
Chatbots unterscheiden sich von Einzel-Frage-Antwort-Aufgaben. Hier ist der Unterschied:
Einzel-Frage-Antwort-Aufgaben:
Bei Einzel-Frage-Antwort-Aufgaben besteht die Interaktion zwischen Benutzer und System in der Regel aus einer einzelnen Frage des Benutzers und einer klaren Antwort des Systems. Diese werden als Frage-Antwort-Paare bezeichnet.
Chatbots:
Die Konversation zwischen einem Chatbot und einem Benutzer ist jedoch komplexer und umfangreicher mit mehreren Dialogen. Chatbots können laufende Dialoge und Folgefragen handhaben und den Kontext der Konversation über mehrere Interaktionen hinweg verfolgen.
Um dies zu erreichen, muss der Chatbot den gesamten Chatverlauf eines Benutzers speichern, einschließlich seiner vorherigen Gespräche und Aktionen (oder Ergebnisse) seiner letzten Funktionsaufrufe. Zweitens sollte der Speicher des Chatbots in der Lage sein, verschiedene Benutzer gleichzeitig zu bedienen und ihre Gespräche voneinander zu trennen. Dies kann eine erhebliche Herausforderung darstellen, wenn es nicht richtig eingerichtet ist. Die gute Nachricht ist, dass MyScale eine perfekte Lösung für diese Herausforderung bietet, indem es seine SQL-Kompatibilität und die Funktion zur rollenbasierten Zugriffskontrolle nutzt, mit der Sie die Chatverläufe von Millionen von Benutzern problemlos verwalten können.
Chatbots können auch von RAG profitieren, aber nicht jeder Chat benötigt RAG. Wenn der Benutzer beispielsweise nach einer Übersetzung von einer Sprache in eine andere fragt, fügt RAG dieser Anfrage keinen Mehrwert hinzu. Daher müssen wir den Chatbot entscheiden lassen, wann und wo er RAG als Teil seiner Suchanfrage verwenden soll.
Wie erreichen wir das?
Glücklicherweise bietet OpenAI eine Funktion-Aufruf-API, die wir verwenden können, um eine Abruf-Pipeline mit MyScale als externem Funktionsaufruf einzufügen.
Darüber hinaus ist MyScale in der Lage, alle Datenhosting-Aufgaben zu erledigen, von der Vektorsuche bis zur Verwaltung des Chatverlaufs. Wie das obige Diagramm zeigt, können Sie einen Chatbot mit MyScale als einziger Datenquelle erstellen. Sie müssen sich keine Sorgen machen, dass Ihre Daten über verschiedene Datenbanken und Engines verteilt sind.
Schauen wir uns also an, wie es gemacht wird!
# Retriever als Werkzeug
RAG kann als externer Funktionsaufruf symbolisiert werden. Weitere Informationen zur Erstellung eines Prompts, um GPT beizubringen, den MyScale-Vektorstore als Werkzeug zu verwenden, finden Sie in unseren Dokumenten zum OpenAI-Funktionsaufruf (opens new window).
Heute werden wir anstelle des Vektorstores die Retrieval-APIs von LangChain verwenden, um Ihre Abfrage mithilfe der erweiterten Filter-Suche von MyScale zu ergänzen. Zuvor haben wir gezeigt, wie selbstabfragende Retriever (opens new window) Ihre Fragen in Vektorsuchabfragen mit Filtern übersetzen können. Wir haben auch beschrieben, wie Retriever, die aus einer Vektor-SQL-Datenbankkette erstellt wurden (opens new window), dasselbe tun wie selbstabfragende Retriever, jedoch in einer SQL-Schnittstelle.
Hinweis:
Diese beiden Retriever nehmen nur den Abfragetext als Eingabe entgegen, daher ist es einfach, sie in Chatbot-Tools umzuwandeln.
Tatsächlich dauert es nur wenige Zeilen Code, um einen Retriever in ein Tool umzuwandeln:
from langchain.agents.agent_toolkits import create_retriever_tool
retriever = ... # selbstabfragender Retriever / Vektor-SQL-Datenbank-Retriever
# Erstellen Sie ein Tool mit einer geeigneten Beschreibung, wobei die Wikipedia-Abrufsuche als Beispiel dient:
tool = create_retriever_tool(retriever,
"search_among_wikipedia",
"Sucht in Wikipedia und gibt verwandte Wiki-Seiten zurück")
# Toolset erstellen
tools = [tool]
Sie können also mehrere Tools erstellen und sie einem einzigen Chatbot zur Verfügung stellen. Wenn Sie beispielsweise mehrere Wissensdatenbanken durchsuchen möchten, können Sie für jede Wissensdatenbank Tools entwickeln und den Chatbot entscheiden lassen, welches Tool verwendet werden soll.
# Die Chats erinnern
Chat-Erinnerungen sind für Chatbots entscheidend. Da wir unserem Chatbot mehrere Tools zur Verfügung stellen, müssen wir auch eine Speicherung der Zwischenergebnisse dieser Tools bereitstellen. Hierbei sind MyScale und LangChain sehr gut.
Das folgende Python-Skript beschreibt, wie Sie eine Speicherung für den Chatbot erstellen können:
from langchain.memory import SQLChatMessageHistory
from langchain.memory.chat_message_histories.sql import BaseMessageConverter, DefaultMessageConverter
from langchain.agents.openai_functions_agent.agent_token_buffer_memory import AgentTokenBufferMemory
# MyScale-Anmeldeinformationen
MYSCALE_USER = ...
MYSCALE_PASSWORD = ...
MYSCALE_HOST = ...
MYSCALE_PORT = ...
database = 'chat'
# MyScale unterstützt sqlalchemy über das Paket `clickhouse-sqlalchemy`
conn_str = f'clickhouse://{MYSCALE_USER}:{MYSCALE_PASSWORD}@{MYSCALE_HOST}:{MYSCALE_PORT}'
# LangChain bietet native Unterstützung für SQL-Datenbanken als Chatverlaufsspeicher
chat_memory = SQLChatMessageHistory(
# Die Sitzungs-ID sollte benutzerspezifisch sein, um Sitzungen zu isolieren
session_id,
# MyScale SaaS verwendet eine HTTPS-Verbindung
connection_string=f'{conn_str}/{database}?protocol=https',
# Hier haben wir den Nachrichtenkonverter und das Tabellenschema angepasst
custom_message_converter=DefaultClickhouseMessageConverter(name))
# AgentTokenBufferMemory hilft uns dabei, alle Zwischennachrichten von Benutzern, Bots und Tools zu speichern
memory = AgentTokenBufferMemory(llm=llm, chat_memory=chat_memory)
MyScale fungiert auch als relationale Datenbank. Dank der SQLChatMessageHistory
von LangChain können Sie MyScale als Ihren Speicherbackend über clickhouse-sqlalchemy
verwenden. Ein angepasster Nachrichtenkonverter ist erforderlich, um mehr Informationen in der Datenbank zu speichern.
So können Sie das Tabellenschema des Speichers definieren:
import time
import json
import hashlib
from sqlalchemy import Column, Text
try:
from sqlalchemy.orm import declarative_base
except ImportError:
from sqlalchemy.ext.declarative import declarative_base
from clickhouse_sqlalchemy import types, engines
from langchain.schema.messages import BaseMessage, _message_to_dict, messages_from_dict
def create_message_model(table_name, DynamicBase): # type: ignore
# Das Modell wird innerhalb einer Funktion deklariert, um einen dynamischen Tabellennamen zu haben
class Message(DynamicBase):
__tablename__ = table_name
# SQLChatMessageHistory ordnet Nachrichten nach ID
# Daher speichern wir hier den Zeitstempel als ID
id = Column(types.Float64)
# Die Sitzungs-ID dient zur Isolierung von Sitzungen
session_id = Column(Text)
# Dies ist der eigentliche Primärschlüssel für die Nachricht
msg_id = Column(Text, primary_key=True)
# Typ dieser Nachricht, könnte HumanMessage / AIMessage oder andere sein
type = Column(Text)
# Zusätzliche Nachricht als JSON-Zeichenkette
addtionals = Column(Text)
# Nachricht im Textformat
message = Column(Text)
__table_args__ = (
# ReplacingMergeTree dedupliziert hinsichtlich des Primärschlüssels
engines.ReplacingMergeTree(
partition_by='session_id',
order_by=('id', 'msg_id')),
{'comment': 'Chatverlauf speichern'}
)
return Message
class DefaultClickhouseMessageConverter(DefaultMessageConverter):
"""Ein ClickHouse-Nachrichtenkonverter für SQLChatMessageHistory."""
def __init__(self, table_name: str):
# Tabellenschema für Chat-Speicher erstellen
self.model_class = create_message_model(table_name, declarative_base())
def to_sql_model(self, message: BaseMessage, session_id: str) -> Any:
tstamp = time.time()
msg_id = hashlib.sha256(f"{session_id}_{message}_{tstamp}".encode('utf-8')).hexdigest()
# Füllen Sie die Lücken aus
return self.model_class(
id=tstamp,
msg_id=msg_id,
session_id=session_id,
type=message.type,
addtionals=json.dumps(message.additional_kwargs),
message=json.dumps({
"type": message.type,
"additional_kwargs": {"timestamp": tstamp},
"data": message.dict()})
)
def from_sql_model(self, sql_message: Any) -> BaseMessage:
# Konvertieren Sie den abgerufenen Verlauf in ein Nachrichtenobjekt
msg_dump = json.loads(sql_message.message)
msg = messages_from_dict([msg_dump])[0]
msg.additional_kwargs = msg_dump["additional_kwargs"]
return msg
Jetzt haben Sie einen voll funktionsfähigen Chat-Speicher, der von MyScale unterstützt wird. Hurra!
# Chat-Speicherverwaltung
Benutzerkonversationsverläufe sind wertvolle Ressourcen und müssen sicher aufbewahrt werden. Die Chat-Erinnerungen von LangChain verfügen bereits über eine Sitzungsisolierung, die durch session_id
gesteuert wird (opens new window).
Millionen von Benutzern könnten mit Ihrem Chatbot interagieren, was die Speicherverwaltung herausfordernd macht. Glücklicherweise haben wir mehrere "Tricks", um die Chatverläufe für all diese Benutzer zu verwalten.
MyScale unterstützt die Datenisolierung durch Erstellung unterschiedlicher Tabellen, Partitionen oder Primärschlüssel für Benutzer. Da zu viele Tabellen das System überlasten würden, empfehlen wir Ihnen, eine metadatenfilterorientierte Multi-Tenancy-Strategie (opens new window) zu verwenden. Konkret können Sie beispielsweise Partitionen anstelle von Tabellen für Ihre Benutzer erstellen oder sie mithilfe eines Primärschlüssels ordnen. Dadurch können Sie schnelle Abfragen aus Ihrer Datenbank durchführen, was effizienter ist als Suchen und Speichern.
In diesem Szenario empfehlen wir die Verwendung der primärschlüsselbasierten Lösung. Durch Hinzufügen von session_id
zur Liste der Primärschlüssel wird die Geschwindigkeit bei der Abfrage des Chatverlaufs eines bestimmten Benutzers verbessert.
# Hier ändern wir das SQLAlchemy-Modell
def create_message_model(table_name, DynamicBase): # type: ignore
class Message(DynamicBase):
__tablename__ = table_name
id = Column(types.Float64)
session_id = Column(Text, primary_key=True)
msg_id = Column(Text, primary_key=True)
type = Column(Text)
addtionals = Column(Text)
message = Column(Text)
__table_args__ = (
engines.ReplacingMergeTree(
# ||| Dadurch werden für jede 1.000 Sitzungen Partitionen erstellt
# vvv (zu viele Partitionen belasten das System)
partition_by='sipHash64(session_id) % 1000',
# Hier ordnen wir nach Sitzungs-ID und Nachrichten-ID
# dadurch wird die Abfrage beschleunigt
order_by=('session_id', 'msg_id')),
{'comment': 'Chatverlauf speichern'}
)
return Message
Hinweis:
Weitere Informationen zu Multi-Tenancy-Strategien (opens new window) finden Sie in unserer Dokumentation.
# Zusammenführen der Komponenten
Jetzt haben wir alle Komponenten, die zum Aufbau eines Chatbots mit RAG benötigt werden. Lassen Sie uns sie zusammenfügen, wie im folgenden Code-Snippet beschrieben:
from langchain.agents import AgentExecutor
from langchain.schema import SystemMessage
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import MessagesPlaceholder
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
# OpenAI LLM initialisieren
chat_model_name = "gpt-3.5-turbo"
OPENAI_API_BASE = ...
OPENAI_API_KEY = ...
# LLM erstellen
chat_llm = ChatOpenAI(model_name=chat_model_name, temperature=0.6, openai_api_base=OPENAI_API_BASE, openai_api_key=OPENAI_API_KEY)
# Start-Prompts, um den Chatbot dazu zu ermutigen, Suchfunktionen zu verwenden
_system_message = SystemMessage(
content=(
"Geben Sie Ihr Bestes, um die Fragen zu beantworten. "
"Verwenden Sie alle verfügbaren Tools, um relevante Informationen zu suchen. "
"Bitte behalten Sie bei Aufrufen von Suchfunktionen alle Details in der Abfrage bei."
)
)
# Funktion-Aufruf-Prompts erstellen
prompt = OpenAIFunctionsAgent.create_prompt(
system_message=_system_message,
# Hier platzieren Sie den Chatverlauf aus der Datenbank
extra_prompt_messages=[MessagesPlaceholde(variable_name="history")],
)
# Wir verwenden den OpenAI-Funktionsagenten
agent = OpenAIFunctionsAgent(llm=chat_llm, tools=tools, prompt=prompt)
# Alle Komponenten zusammenführen
executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=True,
# Wir speichern all diese Zwischenschritte in der Datenbank, daher benötigen wir dies
return_intermediate_steps=True,
)
Da haben Sie es: Einen RAG-fähigen Chatbot mit einem AgentExecutor
. Sie können mit einer einfachen Codezeile mit ihm sprechen:
response = executor({"input": "Hallo!"})
Hinweis: Alle Chatverläufe werden unter executor.memory.chat_memory.messages
gespeichert. Wenn Sie eine Referenz zum Rendern von Nachrichten aus dem Speicher benötigen, können Sie sich unsere Implementierung auf GitHub (opens new window) ansehen.
# Zusammenfassung...
MyScale ist sehr gut für die leistungsstarke Vektorsuche geeignet und bietet alle Funktionen, die SQL-Datenbanken bieten. Sie können es als Vektordatenbank und als SQL-Datenbank verwenden. Darüber hinaus verfügt es über fortschrittliche Funktionen wie Zugriffskontrolle, um Ihre Benutzer und Apps zu verwalten.
Dieser Blog zeigt, wie Sie mit MyScale einen Chatbot aufbauen können, indem Sie es als einzige Datenquelle verwenden. Die Integration Ihres Chatbots mit einer einzigen Datenbank gewährleistet Datenintegrität, Sicherheit und Konsistenz. Es reduziert auch die Redundanz von Daten, indem es auf Datensätze verweist, verbessert den Datenzugriff und das Teilen mit erweiterter Zugriffskontrolle. Dadurch kann die Zuverlässigkeit und Qualität erheblich verbessert werden und Ihr Chatbot wird zu einem modernisierten Service, der sich so skalieren lässt, wie es Ihr Unternehmen benötigt.
Probieren Sie unseren Chatbot auf HuggingFace (opens new window) aus oder führen Sie ihn selbst mit dem Code von GitHub (opens new window) aus! Teilen Sie uns auch Ihre Gedanken auf Twitter (opens new window) und Discord (opens new window) mit.
Referenzen:
- https://myscale.com/blog/de/teach-your-llm-vector-sql/ (opens new window)
- https://myscale.com/docs/de/advanced-applications/chatdata/ (opens new window)
- https://myscale.com/docs/de/sample-applications/openai-function-call/ (opens new window)
- https://python.langchain.com/docs/modules/memory/ (opens new window)
- https://python.langchain.com/docs/integrations/memory/sql_chat_message_history (opens new window)
- https://python.langchain.com/docs/use_cases/chatbots (opens new window)
- https://python.langchain.com/docs/use_cases/question_answering/how_to/conversational_retrieval_agents (opens new window)