Es besteht kein Zweifel, dass ChatGPT von OpenAI außergewöhnlich intelligent ist – es hat die Anwaltsprüfung bestanden, verfügt über Kenntnisse wie ein Arzt und einige Tests haben seinen IQ auf 155 ermittelt . Allerdings tendiert es dazu, Informationen zu fabrizieren , anstatt Unwissenheit einzugestehen. Dieser Trend, gepaart mit der Tatsache, dass sein Wissen im Jahr 2021 eingestellt wird, stellt die Entwicklung spezialisierter Produkte mithilfe der GPT-API vor Herausforderungen.
Wie können wir diese Hindernisse überwinden? Wie können wir einem Modell wie GPT-3 neues Wissen vermitteln? Mein Ziel ist es, diese Fragen zu beantworten, indem ich einen Frage-Antwort-Bot konstruiere, der Python, die OpenAI-API und Worteinbettungen verwendet.
Ich beabsichtige, einen Bot zu erstellen, der aus einer Eingabeaufforderung kontinuierliche Integrationspipelines generiert, die, wie Sie vielleicht wissen, mit YAML in Semaphore CI/CD formatiert sind .
Hier ist ein Beispiel des Bots in Aktion:
Screenshot des laufenden Programms. Auf dem Bildschirm wird der Befehl python query.py "Create a CI pipeline that builds and uploads a Docker image to Docker Hub"
ausgeführt und das Programm gibt YAML aus, das einer CI-Pipeline entspricht, die die angeforderte Aktion ausführt.
Im Geiste von Projekten wie DocsGPT , My AskAI und Libraria habe ich vor, dem GPT-3-Modell Semaphore und das Generieren von Pipeline-Konfigurationsdateien beizubringen. Ich werde dies erreichen, indem ich die vorhandene Dokumentation nutze.
Ich setze keine Vorkenntnisse in der Bot-Erstellung voraus und pflege sauberen Code, damit Sie ihn an Ihre Anforderungen anpassen können.
Sie benötigen keine Erfahrung im Codieren eines Bots oder Kenntnisse über neuronale Netze, um diesem Tutorial folgen zu können. Sie benötigen jedoch:
ChatGPT, oder genauer gesagt GPT-3 und GPT-4, die ihnen zugrunde liegenden Large Language Models (LLMs), wurden anhand eines riesigen Datensatzes trainiert, mit einem Stichtag um September 2021.
Im Wesentlichen weiß GPT-3 sehr wenig über Ereignisse nach diesem Datum. Wir können dies mit einer einfachen Eingabeaufforderung überprüfen:
ChatGPT weiß nicht, wer die Weltmeisterschaft 2022 gewonnen hat.
Während einige OpenAI-Modelle einer Feinabstimmung unterzogen werden können, ist dies bei den fortgeschritteneren Modellen, an denen wir interessiert waren, nicht möglich. Wir können ihre Trainingsdaten nicht erweitern.
Wie können wir von GPT-3 über seine Trainingsdaten hinaus Antworten erhalten? Eine Methode besteht darin, seine Textverständnisfähigkeiten auszunutzen; Indem wir die Eingabeaufforderung mit relevantem Kontext ergänzen, können wir wahrscheinlich die richtige Antwort erhalten.
Im folgenden Beispiel stelle ich Kontext von der offiziellen Website der FIFA bereit, und die Antwort unterscheidet sich erheblich:
Mit dem bereitgestellten Kontext kann ChatGPT genau antworten.
Wir können daraus schließen, dass das Modell auf jede Eingabeaufforderung reagieren kann, wenn genügend relevanter Kontext vorhanden ist. Es bleibt die Frage: Wie können wir bei einer beliebigen Eingabeaufforderung wissen, was relevant ist? Um dieses Problem anzugehen, müssen wir untersuchen, was Worteinbettungen sind.
Im Kontext von Sprachmodellen ist eine Einbettung eine Möglichkeit, Wörter, Sätze oder ganze Dokumente als Vektoren oder Zahlenlisten darzustellen.
Um Einbettungen zu berechnen, benötigen wir ein neuronales Netzwerk wie word2vec oder text-embedding-ada-002 . Diese Netzwerke wurden anhand riesiger Textmengen trainiert und können Beziehungen zwischen Wörtern finden, indem sie die Häufigkeit analysieren, mit der bestimmte Muster in den Trainingsdaten auftreten.
Nehmen wir an, wir haben die folgenden Wörter:
Stellen Sie sich vor, wir verwenden eines dieser Einbettungsnetzwerke, um die Vektoren für jedes Wort zu berechnen. Zum Beispiel:
Wort | Vektor | Kontext |
---|---|---|
Katze | [0,1, 0,2, 0,3, 0,4, 0,5] | Tiere, Gegenstände, kleine Dinge |
Hund | [0,6, 0,7, 0,8, 0,9, 1,0] | Tiere, Gegenstände, große Dinge |
Ball | [0,2, 0,4, 0,6, 0,8, 1,0] | Gegenstände, Spielzeug, Kleinigkeiten |
Haus | [0,3, 0,6, 0,9, 1,2, 1,5] | Gebäude, Häuser, große Dinge |
Sobald wir die Vektoren für jedes Wort haben, können wir sie verwenden, um die Bedeutung des Textes darzustellen. Beispielsweise kann der Satz „Die Katze jagte den Ball“ als Vektor [0,1, 0,2, 0,3, 0,4, 0,5] + [0,2, 0,4, 0,6, 0,8, 1,0] = [0,3, 0,6, 0,9, 1,2] dargestellt werden , 1,5]. Dieser Vektor stellt einen Satz dar, in dem es um ein Tier geht, das einem Objekt nachjagt.
Worteinbettungen können als mehrdimensionale Räume visualisiert werden, in denen Wörter oder Sätze mit ähnlicher Bedeutung nahe beieinander liegen. Wir können den „Abstand“ zwischen Vektoren berechnen, um ähnliche Bedeutungen für jeden Eingabetext zu finden.
3D-Darstellung von Einbettungen als Vektorräume. In Wirklichkeit können diese Räume Hunderte oder Tausende von Dimensionen haben. Quelle: Lernen Sie das Multitool von AI kennen: Vector Embeddings
Die eigentliche Mathematik dahinter würde den Rahmen dieses Artikels sprengen. Die wichtigste Erkenntnis ist jedoch, dass wir mit Vektoroperationen die Bedeutung mithilfe der Mathematik manipulieren oder bestimmen können . Nehmen Sie den Vektor, der das Wort „Königin“ darstellt, subtrahieren Sie den Vektor „Frau“ davon und fügen Sie den Vektor „Mann“ hinzu. Das Ergebnis sollte ein Vektor in der Nähe von „König“ sein. Wenn wir „Sohn“ hinzufügen, sollten wir in die Nähe von „Prinz“ kommen.
Bisher haben wir die Einbettung neuronaler Netze diskutiert, die Wörter als Eingaben und Zahlen als Ausgaben verwenden. Viele moderne Netzwerke sind jedoch von der Verarbeitung von Wörtern zur Verarbeitung von Token übergegangen.
Ein Token ist die kleinste Texteinheit, die vom Modell verarbeitet werden kann. Token können Wörter, Zeichen, Satzzeichen, Symbole oder Wortteile sein.
Wir können sehen, wie Wörter in Token umgewandelt werden, indem wir mit dem OpenAI-Online-Tokenizer experimentieren, der Byte-Pair-Encoding (BPE) verwendet, um Text in Token umzuwandeln und jeden einzelnen durch eine Zahl darzustellen:
Zwischen Token und Wörtern besteht häufig eine 1:1-Beziehung. Die meisten Token enthalten das Wort und ein führendes Leerzeichen. Es gibt jedoch Sonderfälle wie „embedding“, das aus zwei Token besteht, „embed“ und „ding“, oder „capabilities“, das aus vier Token besteht. Wenn Sie auf „Token-IDs“ klicken, können Sie die numerische Darstellung jedes Tokens im Modell sehen.
Nachdem wir nun verstanden haben, was Einbettungen sind, lautet die nächste Frage: Wie können sie uns dabei helfen, einen intelligenteren Bot zu entwickeln?
Betrachten wir zunächst, was passiert, wenn wir die GPT-3-API direkt verwenden. Der Benutzer gibt eine Eingabeaufforderung aus und das Modell reagiert nach besten Kräften.
Wenn wir der Gleichung jedoch Kontext hinzufügen, ändern sich die Dinge. Als ich beispielsweise ChatGPT nach dem Gewinner der Weltmeisterschaft fragte, nachdem ich den Kontext angegeben hatte, machte das den Unterschied.
Der Plan zum Aufbau eines intelligenteren Bots sieht also wie folgt aus:
Beginnen wir wie bei den meisten Projekten mit dem Entwurf der Datenbank.
Unsere Kontextdatenbank muss die Originaldokumentation und ihre jeweiligen Vektoren enthalten. Im Prinzip können wir für diese Aufgabe jede Art von Datenbank verwenden, eine Vektordatenbank ist jedoch das optimale Werkzeug für diese Aufgabe.
Vektordatenbanken sind spezialisierte Datenbanken zum Speichern und Abrufen hochdimensionaler Vektordaten. Anstatt eine Abfragesprache wie SQL für die Suche zu verwenden, stellen wir einen Vektor bereit und fragen die N nächsten Nachbarn ab.
Zur Generierung der Vektoren verwenden wir text-embedding-ada-002 von OpenAI, da es das schnellste und kostengünstigste Modell ist, das sie anbieten. Das Modell wandelt den Eingabetext in Token um und nutzt einen Aufmerksamkeitsmechanismus namens Transformer , um deren Beziehungen zu lernen. Die Ausgabe dieses neuronalen Netzwerks sind Vektoren, die die Bedeutung des Textes darstellen.
Um eine Kontextdatenbank zu erstellen, werde ich:
Zuerst muss ich eine Umgebungsdatei mit dem OpenAI-API-Schlüssel initialisieren. Diese Datei sollte niemals der Versionskontrolle unterliegen, da der API-Schlüssel privat und an Ihr Konto gebunden ist.
export OPENAI_API_KEY=YOUR_API_KEY
Als Nächstes erstelle ich eine virtuelle Umgebung für meine Python-Anwendung:
$ virtualenv venv $ source venv/bin/activate $ source .env
Und installieren Sie das OpenAI-Paket:
```bash $ pip install openai numpy
Versuchen wir, die Einbettung für die Zeichenfolge „Docker Container“ zu berechnen. Sie können dies auf der Python REPL oder als Python-Skript ausführen:
$ python >>> import openai >>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002") >>> embeddings JSON: { "data": [ { "embedding": [ -0.00530336843803525, 0.0013223182177171111, ... 1533 more items ..., -0.015645816922187805 ], "index": 0, "object": "embedding" } ], "model": "text-embedding-ada-002-v2", "object": "list", "usage": { "prompt_tokens": 2, "total_tokens": 2 } }
Wie Sie sehen können, antwortet das Modell von OpenAI mit einer embedding
mit 1536 Elementen – der Vektorgröße für text-embedding-ada-002.
Es stehen zwar mehrere Vektordatenbank-Engines zur Auswahl, wie zum Beispiel Chroma , das Open Source ist, aber ich habe mich für Pinecone entschieden, weil es eine verwaltete Datenbank mit einem kostenlosen Kontingent ist, was die Sache einfacher macht. Ihr Starter-Plan ist mehr als in der Lage, alle Daten zu verarbeiten, die ich benötige.
Nachdem ich mein Pinecone-Konto erstellt und meinen API-Schlüssel und meine Umgebung abgerufen habe, füge ich beide Werte zu meiner .env
Datei hinzu.
Jetzt sollte .env
meine Pinecone- und OpenAI-Geheimnisse enthalten.
export OPENAI_API_KEY=YOUR_API_KEY # Pinecone secrets export PINECONE_API_KEY=YOUR_API_KEY export PINECONE_ENVIRONMENT=YOUR_PINECONE_DATACENTER
Dann installiere ich den Pinecone-Client für Python:
$ pip install pinecone-client
Ich muss eine Datenbank initialisieren; Dies sind die Inhalte des db_create.py
-Skripts:
# db_create.py import pinecone import openai import os index_name = "semaphore" embed_model = "text-embedding-ada-002" api_key = os.getenv("PINECONE_API_KEY") env = os.getenv("PINECONE_ENVIRONMENT") pinecone.init(api_key=api_key, environment=env) embedding = openai.Embedding.create( input=[ "Sample document text goes here", "there will be several phrases in each batch" ], engine=embed_model ) if index_name not in pinecone.list_indexes(): print("Creating pinecone index: " + index_name) pinecone.create_index( index_name, dimension=len(embedding['data'][0]['embedding']), metric='cosine', metadata_config={'indexed': ['source', 'id']} )
Das Erstellen der Datenbank durch das Skript kann einige Minuten dauern.
$ python db_create.py
Als nächstes werde ich das Tiktoken- Paket installieren. Ich werde damit berechnen, wie viele Token die Quelldokumente haben. Dies ist wichtig, da das Einbettungsmodell nur bis zu 8191 Token verarbeiten kann.
$ pip install tiktoken
Während wir Pakete installieren, installieren wir auch tqdm
, um einen gut aussehenden Fortschrittsbalken zu erstellen.
$ pip install tqdm
Jetzt muss ich die Dokumente in die Datenbank hochladen. Das Skript dafür heißt index_docs.py
. Beginnen wir mit dem Importieren der erforderlichen Module und der Definition einiger Konstanten:
# index_docs.py # Pinecone db name and upload batch size index_name = 'semaphore' upsert_batch_size = 20 # OpenAI embedding and tokenizer models embed_model = "text-embedding-ada-002" encoding_model = "cl100k_base" max_tokens_model = 8191
Als nächstes benötigen wir eine Funktion zum Zählen von Token. Auf der OpenAI-Seite gibt es ein Beispiel für einen Token-Zähler :
import tiktoken def num_tokens_from_string(string: str) -> int: """Returns the number of tokens in a text string.""" encoding = tiktoken.get_encoding(encoding_model) num_tokens = len(encoding.encode(string)) return num_tokens
Abschließend benötige ich einige Filterfunktionen, um das Originaldokument in brauchbare Beispiele umzuwandeln. Die meisten Beispiele in der Dokumentation liegen zwischen Code-Fences, daher extrahiere ich einfach den gesamten YAML-Code aus jeder Datei:
import re def extract_yaml(text: str) -> str: """Returns list with all the YAML code blocks found in text.""" matches = [m.group(1) for m in re.finditer("```yaml([\w\W]*?)```", text)] return matches
Ich bin mit den Funktionen fertig. Als nächstes werden die Dateien in den Speicher geladen und die Beispiele extrahiert:
from tqdm import tqdm import sys import os import pathlib repo_path = sys.argv[1] repo_path = os.path.abspath(repo_path) repo = pathlib.Path(repo_path) markdown_files = list(repo.glob("**/*.md")) + list( repo.glob("**/*.mdx") ) print(f"Extracting YAML from Markdown files in {repo_path}") new_data = [] for i in tqdm(range(0, len(markdown_files))): markdown_file = markdown_files[i] with open(markdown_file, "r") as f: relative_path = markdown_file.relative_to(repo_path) text = str(f.read()) if text == '': continue yamls = extract_yaml(text) j = 0 for y in yamls: j = j+1 new_data.append({ "source": str(relative_path), "text": y, "id": f"github.com/semaphore/docs/{relative_path}[{j}]" })
Zu diesem Zeitpunkt sollten alle YAMLs in der Liste new_data
gespeichert sein. Der letzte Schritt besteht darin, die Einbettungen in Pinecone hochzuladen.
import pinecone import openai api_key = os.getenv("PINECONE_API_KEY") env = os.getenv("PINECONE_ENVIRONMENT") pinecone.init(api_key=api_key, enviroment=env) index = pinecone.Index(index_name) print(f"Creating embeddings and uploading vectors to database") for i in tqdm(range(0, len(new_data), upsert_batch_size)): i_end = min(len(new_data), i+upsert_batch_size) meta_batch = new_data[i:i_end] ids_batch = [x['id'] for x in meta_batch] texts = [x['text'] for x in meta_batch] embedding = openai.Embedding.create(input=texts, engine=embed_model) embeds = [record['embedding'] for record in embedding['data']] # clean metadata before upserting meta_batch = [{ 'id': x['id'], 'text': x['text'], 'source': x['source'] } for x in meta_batch] to_upsert = list(zip(ids_batch, embeds, meta_batch)) index.upsert(vectors=to_upsert)
Als Referenz finden Sie die vollständige Datei index_docs.py im Demo-Repository
Lassen Sie uns das Indexskript ausführen, um die Datenbankeinrichtung abzuschließen:
$ git clone https://github.com/semaphoreci/docs.git /tmp/docs $ source .env $ python index_docs.py /tmp/docs
Das Pinecone-Dashboard sollte Vektoren in der Datenbank anzeigen.
Wir können die Datenbank mit dem folgenden Code abfragen, den Sie als Skript oder direkt in der Python REPL ausführen können:
$ python >>> import os >>> import pinecone >>> import openai # Compute embeddings for string "Docker Container" >>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002") # Connect to database >>> index_name = "semaphore" >>> api_key = os.getenv("PINECONE_API_KEY") >>> env = os.getenv("PINECONE_ENVIRONMENT") >>> pinecone.init(api_key=api_key, environment=env) >>> index = pinecone.Index(index_name) # Query database >>> matches = index.query(embeddings['data'][0]['embedding'], top_k=1, include_metadata=True) >>> matches['matches'][0] {'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]', 'metadata': {'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]', 'source': 'docs/ci-cd-environment/docker-authentication.md', 'text': '\n' '# .semaphore/semaphore.yml\n' 'version: v1.0\n' 'name: Using a Docker image\n' 'agent:\n' ' machine:\n' ' type: e1-standard-2\n' ' os_image: ubuntu1804\n' '\n' 'blocks:\n' ' - name: Run container from Docker Hub\n' ' task:\n' ' jobs:\n' ' - name: Authenticate docker pull\n' ' commands:\n' ' - checkout\n' ' - echo $DOCKERHUB_PASSWORD | docker login ' '--username "$DOCKERHUB_USERNAME" --password-stdin\n' ' - docker pull /\n' ' - docker images\n' ' - docker run /\n' ' secrets:\n' ' - name: docker-hub\n'}, 'score': 0.796259582, 'values': []}
Wie Sie sehen können, handelt es sich bei der ersten Übereinstimmung um die YAML für eine Semaphore-Pipeline, die ein Docker-Image abruft und ausführt. Das ist ein guter Anfang, da es für unsere Suchzeichenfolge „Docker Containers“ relevant ist.
Wir haben die Daten und wissen, wie man sie abfragt. Lassen Sie es uns im Bot umsetzen.
Die Schritte zur Verarbeitung der Eingabeaufforderung sind:
Wie üblich definiere ich zunächst einige Konstanten in complete.py
, dem Hauptskript des Bots:
# complete.py # Pinecone database name, number of matched to retrieve # cutoff similarity score, and how much tokens as context index_name = 'semaphore' context_cap_per_query = 30 match_min_score = 0.75 context_tokens_per_query = 3000 # OpenAI LLM model parameters chat_engine_model = "gpt-3.5-turbo" max_tokens_model = 4096 temperature = 0.2 embed_model = "text-embedding-ada-002" encoding_model_messages = "gpt-3.5-turbo-0301" encoding_model_strings = "cl100k_base" import pinecone import os # Connect with Pinecone db and index api_key = os.getenv("PINECONE_API_KEY") env = os.getenv("PINECONE_ENVIRONMENT") pinecone.init(api_key=api_key, environment=env) index = pinecone.Index(index_name)
Als Nächstes füge ich Funktionen zum Zählen von Token hinzu, wie in den OpenAI-Beispielen gezeigt. Die erste Funktion zählt Token in einer Zeichenfolge, während die zweite Funktion Token in Nachrichten zählt. Wir werden die Nachrichten gleich im Detail sehen. Sagen wir zunächst einfach, dass es sich um eine Struktur handelt, die den Stand des Gesprächs im Gedächtnis behält.
import tiktoken def num_tokens_from_string(string: str) -> int: """Returns the number of tokens in a text string.""" encoding = tiktoken.get_encoding(encoding_model_strings) num_tokens = len(encoding.encode(string)) return num_tokens def num_tokens_from_messages(messages): """Returns the number of tokens used by a list of messages. Compatible with model """ try: encoding = tiktoken.encoding_for_model(encoding_model_messages) except KeyError: encoding = tiktoken.get_encoding(encoding_model_strings) num_tokens = 0 for message in messages: num_tokens += 4 # every message follows {role/name}\n{content}\n for key, value in message.items(): num_tokens += len(encoding.encode(value)) if key == "name": # if there's a name, the role is omitted num_tokens += -1 # role is always required and always 1 token num_tokens += 2 # every reply is primed with assistant return num_tokens
Die folgende Funktion verwendet die ursprüngliche Eingabeaufforderung und die Kontextzeichenfolgen, um eine erweiterte Eingabeaufforderung für GPT-3 zurückzugeben:
def get_prompt(query: str, context: str) -> str: """Return the prompt with query and context.""" return ( f"Create the continuous integration pipeline YAML code to fullfil the requested task.\n" + f"Below you will find some context that may help. Ignore it if it seems irrelevant.\n\n" + f"Context:\n{context}" + f"\n\nTask: {query}\n\nYAML Code:" )
Die Funktion get_message
formatiert die Eingabeaufforderung in einem mit der API kompatiblen Format:
def get_message(role: str, content: str) -> dict: """Generate a message for OpenAI API completion.""" return {"role": role, "content": content}
Es gibt drei Arten von Rollen, die die Reaktion des Modells beeinflussen:
Nun zum spannenden Teil. Die Funktion get_context
nimmt die Eingabeaufforderung, fragt die Datenbank ab und generiert eine Kontextzeichenfolge, bis eine dieser Bedingungen erfüllt ist:
context_tokens_per_query
, den Platz, den ich für den Kontext reserviert habe.match_min_score
werden ignoriert. import openai def get_context(query: str, max_tokens: int) -> list: """Generate message for OpenAI model. Add context until hitting `context_token_limit` limit. Returns prompt string.""" embeddings = openai.Embedding.create( input=[query], engine=embed_model ) # search the database vectors = embeddings['data'][0]['embedding'] embeddings = index.query(vectors, top_k=context_cap_per_query, include_metadata=True) matches = embeddings['matches'] # filter and aggregate context usable_context = "" context_count = 0 for i in range(0, len(matches)): source = matches[i]['metadata']['source'] if matches[i]['score'] < match_min_score: # skip context with low similarity score continue context = matches[i]['metadata']['text'] token_count = num_tokens_from_string(usable_context + '\n---\n' + context) if token_count < context_tokens_per_query: usable_context = usable_context + '\n---\n' + context context_count = context_count + 1 print(f"Found {context_count} contexts for your query") return usable_context
Die nächste und letzte Funktion, complete
, gibt die API-Anfrage an OpenAI aus und gibt die Antwort des Modells zurück.
def complete(messages): """Query the OpenAI model. Returns the first answer. """ res = openai.ChatCompletion.create( model=chat_engine_model, messages=messages, temperature=temperature ) return res.choices[0].message.content.strip()
Das ist alles; Jetzt muss ich mich nur noch mit den Kommandozeilenargumenten befassen und die Funktionen in der richtigen Reihenfolge aufrufen:
import sys query = sys.argv[1] context = get_context(query, context_tokens_per_query) prompt = get_prompt(query, context) # initialize messages list to send to OpenAI API messages = [] messages.append(get_message('user', prompt)) messages.append(get_message('system', 'You are a helpful assistant that writes YAML code for Semaphore continuous integration pipelines and explains them. Return YAML code inside code fences.')) if num_tokens_from_messages(messages) >= max_tokens_model: raise Exception('Model token size limit reached') print("Working on your query... ") answer = complete(messages) print("Answer:\n") print(answer) messages.append(get_message('assistant', answer))
Es ist Zeit, das Skript auszuführen und zu sehen, wie es funktioniert:
$ python complete.py "Create a CI pipeline that builds and uploads a Docker image to Docker Hub"
Das Ergebnis ist:
version: v1.0 name: Docker Build and Push agent: machine: type: e1-standard-2 os_image: ubuntu1804 blocks: - name: "Build and Push Docker Image" task: jobs: - name: "Docker Build and Push" commands: - checkout - docker build -t /: . - echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin - docker push /: promotions: - name: Deploy to production pipeline_file: deploy-production.yml auto_promote: when: "result = 'passed' and branch = 'master'"
Das ist das erste gute Ergebnis. Das Modell hat die Syntax aus den von uns bereitgestellten Kontextbeispielen abgeleitet.
Denken Sie daran, dass ich mit einem bescheidenen Ziel begonnen habe: einen Assistenten zum Schreiben von YAML-Pipelines zu erstellen. Mit umfangreicheren Inhalten in meiner Vektordatenbank kann ich den Bot verallgemeinern, um jede Frage zu Semaphore (oder einem anderen Produkt – erinnern Sie sich daran, die Dokumente in /tmp
geklont zu haben?) zu beantworten.
Der Schlüssel zu guten Antworten ist – wenig überraschend – ein qualitativ hochwertiger Kontext. Allein das Hochladen jedes Dokuments in die Vektordatenbank dürfte keine guten Ergebnisse bringen. Die Kontextdatenbank sollte kuratiert, mit beschreibenden Metadaten versehen und prägnant sein. Andernfalls besteht die Gefahr, dass das Token-Kontingent in der Eingabeaufforderung mit irrelevantem Kontext gefüllt wird.
In gewisser Weise ist es also eine Kunst – und eine Menge Versuch und Irrtum –, den Bot an unsere Bedürfnisse anzupassen. Wir können mit der Kontextbeschränkung experimentieren, minderwertige Inhalte entfernen, zusammenfassen und irrelevanten Kontext herausfiltern, indem wir den Ähnlichkeitswert anpassen.
Sie haben vielleicht bemerkt, dass mein Bot uns keine echte Konversation wie ChatGPT ermöglicht. Wir stellen eine Frage und bekommen eine Antwort.
Den Bot in einen vollwertigen Chatbot umzuwandeln, ist im Prinzip keine allzu große Herausforderung. Wir können die Konversation aufrechterhalten, indem wir bei jeder API-Anfrage frühere Antworten erneut an das Modell senden. Frühere GPT-3-Antworten werden unter der Rolle „Assistent“ zurückgesendet. Zum Beispiel:
messages = [] while True: query = input('Type your prompt:\n') context = get_context(query, context_tokens_per_query) prompt = get_prompt(query, context) messages.append(get_message('user', prompt)) messages.append(get_message('system', 'You are a helpful assistant that writes YAML code for Semaphore continuous integration pipelines and explains them. Return YAML code inside code fences.')) if num_tokens_from_messages(messages) >= max_tokens_model: raise Exception('Model token size limit reached') print("Working on your query... ") answer = complete(messages) print("Answer:\n") print(answer) # remove system message and append model's answer messages.pop() messages.append(get_message('assistant', answer))
Leider ist diese Implementierung eher rudimentär. Es werden keine erweiterten Konversationen unterstützt, da die Tokenanzahl mit jeder Interaktion steigt. Schon bald werden wir das 4096-Token-Limit für GPT-3 erreichen, was einen weiteren Dialog verhindert.
Wir müssen also eine Möglichkeit finden, die Anfrage innerhalb der Token-Grenzen zu halten. Es folgen einige Strategien:
Die Antworten des Bots können durch Worteinbettungen und eine gute Kontextdatenbank verbessert werden. Um dies zu erreichen, benötigen wir eine qualitativ hochwertige Dokumentation. Die Entwicklung eines Bots, der scheinbar ein Verständnis für die Thematik besitzt, ist mit einem erheblichen Aufwand an Versuch und Irrtum verbunden.
Ich hoffe, dass diese eingehende Untersuchung der Worteinbettungen und großen Sprachmodelle Ihnen dabei hilft, einen leistungsfähigeren Bot zu erstellen, der auf Ihre Anforderungen zugeschnitten ist.
Viel Spaß beim Bauen!
Auch hier veröffentlicht.