paint-brush
So erstellen Sie einen Agenten mit einem OpenAI-Assistenten in Python – Teil 1: Konversationalvon@jeanmaried
4,035 Lesungen
4,035 Lesungen

So erstellen Sie einen Agenten mit einem OpenAI-Assistenten in Python – Teil 1: Konversational

von Jean-Marie Dalmasso12m2024/02/09
Read on Terminal Reader

Zu lang; Lesen

Wir führen nun eine While-Schleife aus, um den Status „Abgeschlossen“ zu prüfen und gleichzeitig einige Fehlerszenarien zu behandeln. Die tatsächliche Abrechnung der Assistant API ist etwas unklar, daher habe ich mich sicherheitshalber dafür entschieden, meine Läufe nach 2 Minuten abzubrechen. Auch wenn es einen abgelaufenen Status gibt, wenn OpenAI Ausführungen nach 10 Minuten abbricht. Wenn ein Lauf länger als 2 Minuten dauert, liegt wahrscheinlich sowieso ein Problem vor.
featured image - So erstellen Sie einen Agenten mit einem OpenAI-Assistenten in Python – Teil 1: Konversational
Jean-Marie Dalmasso HackerNoon profile picture

Dies ist der erste Teil einer mehrteiligen Serie zum Erstellen von Agenten mit der Assistant-API von OpenAI unter Verwendung des Python SDK.

Was sind Agenten?

Meiner Meinung nach ist ein Agent eigentlich nur eine Software, die ein LLM (Large Language Model) nutzt und versucht, menschliches Verhalten nachzuahmen. Das heißt, es kann sich nicht nur unterhalten und Sprache verstehen, sondern auch Handlungen ausführen, die Auswirkungen auf die reale Welt haben. Diese Aktionen werden normalerweise als Tools bezeichnet.


In diesem Blogbeitrag werden wir untersuchen, wie man mithilfe der Assistant-API von OpenAI und dem Python SDK einen Agenten erstellt. Teil 1 wird nur das Skelett des Assistenten sein. Das heißt, nur der Konversationsteil.


Ich habe mich bewusst dafür entschieden, eine CLI-App zu erstellen, um Framework-unabhängig zu sein. Wir werden unsere Implementierung absichtlich als Agent bezeichnen und die OpenAI SDK-Implementierung als Assistent bezeichnen, um die beiden leicht unterscheiden zu können.


Ich verwende die Begriffe Tools und Funktionen synonym, wenn es um Funktionen geht, die der Agent aufrufen kann. In Teil 2 wird der Funktionsaufruf ausführlicher behandelt.

Voraussetzungen

Um diesem Tutorial folgen zu können, benötigen Sie Folgendes:


  • Python3 ist auf Ihrem Computer installiert
  • Ein OpenAI-API-Schlüssel
  • Grundkenntnisse der Python-Programmierung

OpenAI-Assistentenkonzepte

Assistent : Ein Assistent in der Assistenten-API ist eine Entität, die so konfiguriert ist, dass sie auf Benutzernachrichten reagiert. Es nutzt Anweisungen, ein ausgewähltes Modell und Werkzeuge, um mit Funktionen zu interagieren und Antworten zu geben.


Thread : Ein Thread stellt eine Konversation oder einen Dialog in der Assistants-API dar. Es wird für jede Benutzerinteraktion erstellt und kann mehrere Nachrichten enthalten und als Container für die laufende Konversation dienen.


Nachricht : Eine Nachricht ist eine Kommunikationseinheit in einem Thread. Es enthält Text (und möglicherweise zukünftige Dateien) und wird verwendet, um Benutzeranfragen oder Assistentenantworten innerhalb eines Threads zu übermitteln.


Run : Ein Run ist eine Instanz des Assistenten, der einen Thread verarbeitet. Dazu gehört das Lesen des Threads, die Entscheidung, ob Tools aufgerufen werden sollen, und das Generieren von Antworten basierend auf der Interpretation der Thread-Nachrichten durch das Modell.

Einrichten der Entwicklungsumgebung

Der erste Schritt besteht darin, mit venv eine virtuelle Umgebung zu erstellen und diese zu aktivieren. Dadurch wird sichergestellt, dass unsere Abhängigkeiten von der System-Python-Installation isoliert sind:

 python3 -m venv venv source venv/bin/activate


Installieren wir unsere einzige Abhängigkeit: das openai Paket:

 pip install openai


Erstellen Sie eine main.py Datei. Lassen Sie uns einige grundlegende Laufzeitlogik für unsere CLI-App auffüllen:

 while True: user_input = input("User: ") if user_input.lower() == 'exit': print("Exiting the assistant...") break print(f"Assistant: You said {user_input}")


Probieren Sie es aus, indem Sie python3 main.py ausführen:

 python3 main.py User: hi Assistant: You said hi


Wie Sie sehen können, akzeptiert die CLI eine Benutzernachricht als Eingabe und unser genialer Assistent hat noch kein Gehirn 🧠, also wiederholt er die Nachricht einfach gleich. Noch nicht so schlau.

Der Agent

Jetzt beginnt der Spaß 😁 (oder die Kopfschmerzen 🤕). Ich werde jetzt alle für die letzte Klasse benötigten Importe bereitstellen, damit Sie sich nicht den Kopf darüber zerbrechen müssen, woher die Dinge kommen, da ich der Kürze halber Importe aus den Codebeispielen herausgehalten habe. Beginnen wir mit dem Erstellen einer Agent Klasse in einer neuen Datei agent.py :

 import time import openai from openai.types.beta.threads.run import Run class Agent: def __init__(self, name: str, personality: str): self.name = name self.personality = personality self.client = openai.OpenAI(api_key="sk-*****") self.assistant = self.client.beta.assistants.create( name=self.name, model="gpt-4-turbo-preview" )


Im Klassenkonstruktor initialisieren wir den OpenAI-Client als Klasseneigenschaft, indem wir unseren OpenAI-API-Schlüssel übergeben. Als Nächstes erstellen wir eine Eigenschaft assistant Klasse, die unserem neu erstellten Assistant zugeordnet ist. Wir speichern name und personality als Klasseneigenschaften zur späteren Verwendung.


Das name , das wir an die Methode „create“ übergeben, dient lediglich der Identifizierung des Assistenten im OpenAI-Dashboard, und die KI ist sich dessen zu diesem Zeitpunkt noch nicht wirklich bewusst. Sie müssen den Namen tatsächlich an die instructions übergeben, die wir später sehen werden.


Sie könnten bereits beim Erstellen des Assistenten instructions festlegen, aber dadurch wird Ihr Assistent tatsächlich weniger flexibel gegenüber dynamischen Änderungen.


Sie können einen Assistenten aktualisieren, indem Sie client.beta.assistants.update aufrufen, aber es gibt einen besseren Ort, um dynamische Werte zu übergeben, die wir sehen werden, wenn wir zu „Runs“ kommen.


Beachten Sie, dass die instructions des Assistenten durch die instructions des Laufs überschrieben werden, wenn Sie beim Erstellen eines Laufs hier und dann wieder instructions übergeben. Sie ergänzen einander nicht. Wählen Sie daher je nach Bedarf eine aus: Assistentenebene für statische Anweisungen oder Ausführungsebene für dynamische Anweisungen.


Als Modell habe ich das gpt-4-turbo-preview Modell ausgewählt, damit wir in Teil 2 dieser Serie Funktionsaufrufe hinzufügen können. Sie könnten gpt-3.5-turbo verwenden, wenn Sie ein paar Bruchteile eines Cents sparen möchten und sich gleichzeitig eine Migräne purer Frustration bescheren möchten, wenn wir Tools implementieren.


GPT 3.5 ist beim Aufrufen von Tools schrecklich; Die Stunden, die ich damit verloren habe, damit umzugehen, erlauben es mir, das zu sagen. 😝 Ich belasse es dabei, dazu später mehr.

Einen Thread erstellen, Nachrichten hinzufügen und die letzte Nachricht abrufen

Nachdem wir einen Agenten erstellt haben, müssen wir einen Konversationsthread starten.

 class Agent: # ... (rest of code) def create_thread(self): self.thread = self.client.beta.threads.create()


Und wir suchen nach einer Möglichkeit, diesem Thread Nachrichten hinzuzufügen:

 class Agent: # ... (rest of code) def add_message(self, message): self.client.beta.threads.messages.create( thread_id=self.thread.id, role="user", content=message )


Beachten Sie, dass das Hinzufügen von Nachrichten derzeit nur mit der Rolle user möglich ist. Ich glaube, OpenAI plant, dies in einer zukünftigen Version zu ändern, da dies ziemlich einschränkend ist.


Jetzt können wir die letzte Nachricht im Thread erhalten:

 class Agent: # ... (rest of code) def get_last_message(self): return self.client.beta.threads.messages.list( thread_id=self.thread.id ).data[0].content[0].text.value


Als Nächstes erstellen wir eine run_agent Einstiegspunktmethode, um zu testen, was wir bisher haben. Derzeit gibt die run_agent Methode nur die letzte Nachricht im Thread zurück. Es führt eigentlich keinen Lauf durch. Es ist immer noch hirnlos.

 class Agent: # ... (rest of code) def run_agent(self): message = self.get_last_message() return message


Zurück in main.py erstellen wir den Agenten und unseren ersten Thread. Wir fügen dem Thread eine Nachricht hinzu. Senden Sie dann dieselbe Nachricht an den Benutzer zurück, diesmal jedoch aus diesem Live-Thread.

 from agent import Agent agent = Agent(name="Bilbo Baggins", personality="You are the accomplished and renowned adventurer from The Hobbit. You act like you are a bit of a homebody, but you are always up for an adventure. You worry a bit too much about breakfast.") agent.create_thread() while True: user_input = input("User: ") if user_input.lower() == 'exit': print("Exiting the agent...") break agent.add_message(user_input) answer = agent.run_agent() print(f"Assistant: {answer}")


Lassen Sie es uns ausführen:

 python3 main.py User: hi Assistant: hi


Immer noch nicht sehr schlau. Eher einem Papagei 🦜 als einem Hobbit. Im nächsten Abschnitt beginnt der wahre Spaß.

Erstellen und Abfragen eines Laufs

Wenn Sie einen Lauf erstellen, müssen Sie das Run Objekt regelmäßig abrufen, um den Status des Laufs zu überprüfen. Das nennt man Polling und es ist scheiße. Sie müssen eine Umfrage durchführen, um festzustellen, was Ihr Agent als Nächstes tun soll. OpenAI plant, Unterstützung für Streaming hinzuzufügen, um dies einfacher zu machen. In der Zwischenzeit werde ich Ihnen im nächsten Abschnitt zeigen, wie Sie die Umfrage einrichten.


Beachten Sie das _ in den folgenden Methodennamen, das der Standard in Python ist und anzeigt, dass die Methode für den internen Gebrauch gedacht ist und nicht direkt durch externen Code aufgerufen werden sollte.


Erstellen wir zunächst eine Hilfsmethode _create_run zum Erstellen eines Run und aktualisieren wir run_agent , um diese Methode aufzurufen:

 class Agent: # ... (rest of code) def get_breakfast_count_from_db(self): return 1 def _create_run(self): count = self.get_breakfast_count_from_db() return self.client.beta.threads.runs.create( thread_id=self.thread.id, assistant_id=self.assistant.id, instructions=f""" Your name is: {self.name} Your personality is: {self.personality} Metadata related to this conversation: {{ "breakfast_count": {count} }} """, ) def run_agent(self): run = self._create_run() # add this line message = self.get_last_message() return message


Beachten Sie, wie wir thread.id und assistant.id übergeben, um einen Lauf zu erstellen.


Erinnern Sie sich, wie ich am Anfang sagte, dass es einen besseren Ort für die Weitergabe dynamischer Anweisungen und Daten gibt? Das wäre der instructions beim Erstellen des Laufs. In unserem Fall könnten wir die count der Frühstücke aus einer Datenbank abrufen lassen. Auf diese Weise können Sie jedes Mal, wenn Sie eine Antwort auslösen möchten, problemlos verschiedene relevante dynamische Daten übergeben.


Jetzt ist sich Ihr Agent bewusst, dass sich die Welt um ihn herum verändert, und kann entsprechend handeln. Ich möchte in meinen Anweisungen ein Metadaten-JSON-Objekt haben, das den relevanten dynamischen Kontext beibehält. Dadurch kann ich Daten mit weniger Ausführlichkeit und in einem Format weitergeben, das der LLM wirklich gut versteht.


Führen Sie dies noch nicht aus; Es wird nicht funktionieren, weil wir nicht auf den Abschluss des Laufs warten, wenn wir die letzte Nachricht erhalten, es also immer noch die letzte Benutzernachricht sein wird.


Lassen Sie uns dieses Problem lösen, indem wir unseren Umfragemechanismus ausbauen. Zuerst benötigen wir eine Möglichkeit, einen Lauf wiederholt und einfach abzurufen. Fügen wir also eine _retrieve_run -Methode hinzu:

 class Agent: # ... (rest of code) def _retrieve_run(self, run: Run): return self.client.beta.threads.runs.retrieve( run_id=run.id, thread_id=self.thread.id)


Beachten Sie, dass wir sowohl run.id als auch thread.id übergeben müssen, um einen bestimmten Lauf zu finden.


Fügen Sie unserer Agent-Klasse eine _poll_run Methode hinzu:

 class Agent: # ... (rest of code) def _cancel_run(self, run: Run): self.client.beta.threads.runs.cancel( run_id=run.id, thread_id=self.thread.id) def _poll_run(self, run: Run): status = run.status start_time = time.time() while status != "completed": if status == 'failed': raise Exception(f"Run failed with error: {run.last_error}") if status == 'expired': raise Exception("Run expired.") time.sleep(1) run = self._retrieve_run(run) status = run.status elapsed_time = time.time() - start_time if elapsed_time > 120: # 2 minutes self._cancel_run(run) raise Exception("Run took longer than 2 minutes.")


🥵 Puh, das ist eine ganze Menge... Packen wir es mal aus.


_poll_run empfängt ein Run Objekt als Argument und extrahiert den aktuellen Run- status . Alle verfügbaren Status finden Sie in den OpenAI- Dokumenten . Wir werden nur einige verwenden, die unserem aktuellen Zweck entsprechen.


Wir führen nun eine While-Schleife aus, um den Status „Abgeschlossen“ zu prüfen und gleichzeitig einige Fehlerszenarien zu behandeln. Die tatsächliche Abrechnung der Assistant API ist etwas unklar, daher habe ich mich sicherheitshalber dafür entschieden, meine Läufe nach 2 Minuten abzubrechen.


Auch wenn es einen expired Status gibt, wenn OpenAI Ausführungen nach 10 Minuten abbricht. Wenn ein Lauf länger als 2 Minuten dauert, liegt wahrscheinlich sowieso ein Problem vor.


Da ich auch nicht alle paar Millisekunden abfragen möchte, drossle ich meine Anfrage, indem ich nur alle 1 Sekunde abfrage, bis ich die 2-Minuten-Marke erreiche und meinen Lauf abbreche. Sie können dies nach Ihren Wünschen anpassen.


Bei jeder Iteration nach der Verzögerung rufen wir den Ausführungsstatus erneut ab.


Fügen wir das alles nun in unsere run_agent Methode ein. Sie werden feststellen, dass wir zuerst den Lauf mit _create_run erstellen und dann mit _poll_run abfragen, bis wir eine Antwort erhalten oder ein Fehler ausgegeben wird. Wenn die Abfrage abgeschlossen ist, rufen wir schließlich die letzte Nachricht aus dem Thread ab, die nun vom Agenten stammt.


Anschließend geben wir die Nachricht an unsere Laufzeitschleife zurück, damit sie an den Benutzer zurückgesendet werden kann.

 class Agent: # ... (rest of code) def run_agent(self): run = self._create_run() self._poll_run(run) # add this line message = self.get_last_message() return message


Voilà, wenn Sie jetzt Ihren Agenten erneut ausführen, erhalten Sie eine Antwort von unserem freundlichen Agenten:

 python3 main.py User: hi Assistant: Hello there! What adventure can we embark on today? Or perhaps, before we set out, we should think about breakfast. Have you had yours yet? I've had mine, of course – can't start the day without a proper breakfast, you know. User: how many breakfasts have you had? Assistant: Ah, well, I've had just 1 breakfast today. But the day is still young, and there's always room for a second, isn't there? What about you? How can I assist you on this fine day?


In Teil 2 werden wir unserem Agenten die Möglichkeit hinzufügen, Tools aufzurufen.


Den vollständigen Code finden Sie auf meinem GitHub .


Vielen Dank für Ihre Lektüre. Ich freue mich über alle Gedanken und Rückmeldungen in den Kommentaren. Folgen Sie mir auf Linkedin für weitere Inhalte wie diesen: https://www.linkedin.com/in/jean-marie-dalmasso-1b5473141/