Wie ich mich mit einem KI-Agenten aus dem Coding-Treibsand befreit habe
Ich stürzte mich mit null Coding-Erfahrung und viel Enthusiasmus ins Bauen eines Chatbots — nur um festzustellen, dass meine v0.1 ein Desaster aus CSV-Datenbanken und primitivem Chunking war, bis KI-Agenten mich herausholten.
Update (2026): Dieser Chatbot hat sich zu Sydney entwickelt! Nach vielen Iterationen lebt Sydney jetzt unter /ask/ und konzentriert sich auf Blog-Inhalte und Produkte.
Anfang November letzten Jahres habe ich meinen DIY-Chatbot Version 0.1 veröffentlicht. Damals schrieb ich: „Während v0.1 als Coding-Neuling einen großen ersten Schritt darstellte, hatte er erhebliche Einschränkungen." Wie sich herausstellte, ist das eine Untertreibung. Ich hatte nicht erkannt, wie umständlich der gesamte Prozess und mein Build waren. Die Realität war, dass mein erster Versuch, obwohl aufrichtig, eher ein Prototyp war, der mit Enthusiasmus, aber begrenztem Know-how zusammengestückelt wurde.
Dieser Beitrag ist keine bloße Nachbetrachtung; er ist ein tiefer Einblick in die Reise, die sich von diesem Punkt an entfaltete — eine Reise voller Versuche, Fehler und unschätzbarer Lektionen. Ich lege die Einzelheiten dieses Abenteuers offen, nicht nur der Transparenz halber, sondern in der Hoffnung, dass meine Erfahrungen, so detailliert sie sind, bei jemandem auf einem ähnlichen Weg Anklang finden oder ihm sogar helfen könnten. (Zum Kontext: Ich bin ein mittelalter Werbefachmann ohne vorherige Coding-Erfahrung.)
Wie erwähnt, folgte ich beim Erstellen von v0.1 meines Chatbots hauptsächlich den Anweisungen aus diesem Kurzlehrgang „Building Systems with the ChatGPT API" und zwei Cookbooks von OpenAI: Question answering using embeddings-based search und How to count tokens with tiktoken.
Hier ist, warum mein Chatbot v0.1 schrecklich ist:
- Chunking: Ich habe lange Blog-Beiträge einfach in kleinere Chunks aufgeteilt, basierend auf der Token-Länge, also einfaches statisches Zeichen-Chunking. Die primitivste Aufteilungsmethode überhaupt :D. Wenn du verstehen möchtest, warum das eine schreckliche Idee ist, lies über 5 Levels of Text Splitting von Greg Kamradt hier.
- Embedding: Ich hatte Schwierigkeiten mit Embeddings. Ich verwendete OpenAIs Embedding-Modell, stieß aber immer wieder auf API-Request-Limits, was dazu führte, dass der Embedding-Prozess auf halbem Weg scheiterte. Dann lernte ich, Anfragen zu bündeln und Timeouts zwischen Batches hinzuzufügen, um Limits zu vermeiden. Letztendlich speicherte ich die generierten Embeddings in einer einfachen .csv-Datei als meine provisorische „Datenbank".
- Datenbank: Ich wusste, dass eine CSV für eine Datenbank nicht optimal war, aber mir fehlten die Fähigkeiten für bessere Alternativen.
- Metadaten: Ich hatte anfangs nicht erkannt, dass das Einbeziehen von Metadaten wie Veröffentlichungsdaten und Beitrags-URLs wichtig ist, damit Chatbots Benutzerfragen genau beantworten können. Ich musste die Embedding- und Speichervorgänge wiederholen, um relevante Metadaten einzubeziehen.
- Retriever: Ich war mir verschiedener Retriever-Typen und -Algorithmen nicht bewusst. Ich verwendete einfach OpenAIs Relevanzsuche, um eine fest kodierte Anzahl von Ergebnissen abzurufen.
- Speicher: Um ein Gespräch zu führen, muss der Chatbot sich merken können, was der Benutzer zuvor gesagt hat. Und hier gab es mit einem begrenzten Kontextfenster von gpt-3.5 (damals) einen klaren Trade-off zwischen Chunk-Größe und der Anzahl der abzurufenden Ergebnisse.
- Wenn deine Chunk-Größe zum Beispiel 800 Token beträgt und der Retriever die Top-8-Ergebnisse zurückgibt, sind das 6.400 Token oder mehr als 50 % des alten Modelllimits.
- Das Obige ist nur 1 Frage, du kannst dir also vorstellen, wie bei einer mehrstufigen Konversation der Speicher sehr schnell gefüllt werden kann.
- Eine Möglichkeit, dieses Problem zu lösen, besteht darin, eine kleinere Chunk-Größe zu haben und den Retriever weniger Ergebnisse zurückgeben zu lassen, aber mit einem einfachen Retriever (oben) bedeutet das, dass das Modell keine umfassenden Informationen hat, um die Frage zu beantworten.
- Ich habe nicht mal eine IDE benutzt. Alle Codes wurden mit TextEdit auf dem Mac bearbeitet :D (Habe ich erwähnt, dass ich vorher ein Neuling war? :P)
- Ich könnte noch mehr aufzählen, aber ich denke, du verstehst den Punkt.
Mein „Tal des Todes"
In dem Wunsch, über die Grenzen von v0.1 hinaus zu verbessern, versuchte ich mehrere Online-Kurse, in der Hoffnung, dass sie mir die fehlenden Teile liefern würden, um meine Fähigkeiten zu verbessern. Aber der Fortschritt führte nur zu Sackgassen.
Ich kämpfte mich durch Übungen in Vektor-Datenbanken (Vector Databases: from Embeddings to Applications with Weaviate), evaluative RAG-Methoden (Building and Evaluating Advanced RAG Applications mit Llama-Index und Truera) und fortgeschrittene Retrieval-Techniken (Advanced Retrieval for AI with Chroma). Wie sehr ich es auch versuchte, ich konnte die Theorie nicht mit der praktischen Anwendung auf meine eigenen Blog-Daten verbinden.
Waren die Kurse schlecht gestaltet? Nein — der Mangel lag in meinem eigenen fehlenden Grundlagenwissen. Dennoch war Misserfolg nach Misserfolg massiv frustrierend, von der Demoralisierung ganz zu schweigen. Ich befand mich in einem bildlichen Tal und wusste nicht, wie ich weitermachen sollte.
Schrittweise Fortschritte über die Zeit
Aus wiederholten Fehlschlägen kristallisierten sich jedoch einige Wertnuggets heraus:
-
Übernahme von VS Code anstelle von TextEdit
-
Nutzung von GitHub Copilot Extensions
-
Wertschätzung von Jupyter Notebook für Entwicklungsumgebungen
Der letzte erwähnte Kurs sprach LangChain an, ein beliebtes neues Framework für den Bau von Chatbots. Ich hatte tatsächlich Monate zuvor LangChain-Tutorials versucht („LangChain: Chat with Your Data" und „Functions, Tools and Agents with LangChain") ohne viel Erfolg. Aber jetzt, mit harterkämpftem Wissen in meinem Repertoire, erwies sich die erneute Lektüre der Docs als erhellend. Konzepte klickten zusammen und seine modulare Architektur ergab intuitiv Sinn.
Ich konnte mir vorstellen, LangChains robuste Fähigkeiten an mein Leidenschaftsprojekt anzupassen. Endlich zeigte sich ein Weg nach vorne! Schritt für Schritt orientierte ich mich mit seinen Pipelines für Datenaufnahme, Embedding, Speicher und Retrieval.
Mein Vertrauen wuchs mit jedem Teil, den ich umsetzen konnte. V2 begann Gestalt anzunehmen...
Die Grundlage neu aufbauen
Mit Langchain als Leitfaden machte ich mich daran, meinen Chatbot von Grund auf neu zu bauen:
WordPress-Exporte in JSON einlesen
Nach einigen Versuchen und Fehlern beim Anpassen der Aufnahmeparameter analysierte LangChains JSONLoader meine exportierten Beiträge korrekt. Jetzt konnte validierter Input nachgelagerte Pipelines speisen. (Der Code für diesen Schritt befindet sich in der Datei „DataIngestionAndIndexing.ipynb" in diesem öffentlichen Github-Repo.)
Automatisiertes Text-Splitting
Mein naives Token-Längen-Chunking wurde durch LangChains SentenceTransformers ersetzt, die fortgeschrittenes NLP nutzen, um semantische Einheiten aufzuteilen. Keine auf halbem Weg abgeschnittenen Sätze mehr! Konfigurationen hielten Chunks in angemessener Größe für speicherbeschränkte Modelle.
from langchain.text_splitter import SentenceTransformersTokenTextSplitter
# Define the token splitter with specific configurations
token_splitter = SentenceTransformersTokenTextSplitter(
chunk_overlap=0, # Overlap between chunks
tokens_per_chunk=256 # Number of tokens per chunk
)
# Split the documents into chunks based on tokens
all_splits = token_splitter.split_documents(documents)
print(f"Total document splits: \{len(all_splits)\}")
Embeddings und Indizes generieren
Frühere Schwierigkeiten mit OpenAI API-Limits verschwanden durch LangChains Wrapper für OpenAI-Embeddings. In zwei Codezeilen gebündelt, extrahierten Embeddings sauber relevante Merkmale aus dem gesplitteten Text.
Als Vektor-Store entschied ich mich für FAISS (Facebook AI Similarity Search) gegenüber Weaviate oder Chroma. Das bewährte FAISS traf die richtige Balance aus Leistungsfähigkeit und Komplexität für meine Bedürfnisse. Seine CPU-Version indizierte Blog-Chunks schnell und gab eine kompakte, durchsuchbare Datenbank aus. Ich musste mir keine Gedanken mehr über Batching oder das Erreichen des API-Request-Limits bei OpenAI machen.
Langchain unterstützt mehrere Vektor-Stores, du kannst sie hier einsehen.
# Initialize embeddings and FAISS vector store
embeddings = OpenAIEmbeddings()
db = FAISS.from_documents(all_splits, embeddings)
# Save the vector store locally
db.save_local("path/to/save/faiss_index") # Placeholder for save path/ index name
Zwei Codezeilen! Das ist alles.
Was ich auch bemerkt habe: Wenn dein Inhalt nicht klein ist (ich habe 400+ Blog-Beiträge), solltest du den Retriever nicht unmittelbar nach dem Embedding verwenden, weil die Indizes etwas Zeit brauchen, um abgeschlossen/stabil zu werden.
Retriever evaluieren
Das Einrichten dieses Retrievers dauert nur 1 Codezeile :D, mit FAISS als Vektor-Store.
retriever = db.as_retriever(search_type="mmr")
Mit meinem Index und Retriever hatte ich Datenpipelines bereit, um einen intelligenten Chatbot zu befeuern!
Konversationsagenten aufbauen
Ich entschied mich, das Agent-Framework von Langchain zu verwenden, um diesen Chatbot zu bauen. Ist das an diesem Punkt übertrieben? Ja, das ist es. Aber meine Hoffnung ist, dass ich diesen Chatbot im Laufe der Zeit weiterentwickeln und ihm mehr „Tools" also Funktionalitäten geben kann. Langchain macht es super einfach, den Agenten einzurichten und ihm Tools zur Verfügung zu stellen.
embeddings = OpenAIEmbeddings()
db = FAISS.load_local("path/to/your/faiss_index_file", embeddings) # Replace the path with your actual FAISS index file path
retriever = db.as_retriever(search_type="mmr")
tool = create_retriever_tool(
retriever,
"search_your_blog", # Replace "search_your_blog" with a descriptive name for your tool
"Your tool description here" # Provide a brief description of what your tool does
)
tools = [tool]
prompt_template = ChatPromptTemplate.from_messages([
# Customize the prompt template according to your chatbot's persona and requirements
])
llm = ChatOpenAI(model_name="gpt-3.5-turbo-1106", temperature=0)
llm_with_tools = llm.bind_tools(tools)
agent = (
\{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
"chat_history": lambda x: x["chat_history"],
\}
| prompt_template
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
Der vollständige, finale Python-Code für den Agenten
Zu guter Letzt: Wenn du den Chatbot v2 ausprobieren möchtest, findest du ihn hier.
Ist es seltsam, dass der Chatbot nichts über dich weiß, Chandler?
P.S: Danke an einige von euch, die sich gemeldet haben, um mir mitzuteilen, dass der Chatbot nichts über mich weiß. Und ihr habt recht! Das liegt daran, dass ich vergessen habe, die „About"-Seite zu exportieren, und nur die „veröffentlichten Beiträge" exportiert habe. Das ist das zweite Mal, dass ich das vergessen habe, also werde ich grundlegende Fragen über mich in die Liste der Eval-Fragen aufnehmen. Lektion gelernt!
Danke an alle für das konstruktive Feedback. Bitte macht weiter damit. Und ja, ich weiß, dass der Chatbot sehr langsam startet, also arbeite ich auch daran. :| (Habe ich erwähnt, dass ich vorher ein Neuling war? :P)
Ein kurzes Update
Das Problem, dass der Chatbot nichts über mich weiß, ist jetzt behoben. Das habe ich getan und gelernt:
- Die „About me"-Seite aus Wordpress in .XML exportieren, wie oben beschrieben.
- Text-Splitting durchgeführt und Embeddings mit FAISS generiert, wie oben. Den Vektor-Store unter einem anderen Namen lokal gespeichert, um den Retriever zu testen
# save the vector store to local machine
db.save_local("faiss_index_about")
# set up the retriever to test the new vector store about Chandler
from langchain.retrievers.multi_query import MultiQueryRetriever
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=db.as_retriever(), llm=llm
)
# test retriever
question = "Who is Chandler Nguyen?"
results = retriever_from_llm.get_relevant_documents(query=question, top_k=8)
for doc in results:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
- Wie sich herausstellte, ist der Prozess zum Zusammenführen zweier FAISS-Vektor-Stores überraschend einfach, laut der Dokumentation hier.
# Try to merge two FAISS vector stores into 1
# load the vector store from local machine
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
db_1 = FAISS.load_local("faiss_index_about", embeddings)
db_2 = FAISS.load_local("faiss_index", embeddings)
db_2.merge_from(db_1)
# save the vector store to local machine
db_2.save_local("faiss_index_v2")
# test the new vector store to confirm correct retrieved documents
retriever = db_2.as_retriever(search_type="mmr")
results = retriever.get_relevant_documents("Who is Chandler Nguyen?")
for doc in results:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
- Danach ist es im Grunde derselbe Prozess wie oben, mit dem neuen Vektor-Store
Feb 14 Update: Chatbot v2.10 enthüllt
Zwei Wochen nach dieser Bereitstellung des Chatbots habe ich Version 2.10 eingeführt, die das Benutzererlebnis mit verbesserter Geschwindigkeit, Skalierbarkeit und Einfachheit auf ein neues Level hebt. Du kannst mehr darüber hier lesen.
Mär 25 Update: Vom Frontend-Upgrade zu Docker-Kämpfen und Durchbrüchen
Du kannst mehr darüber hier lesen.





