Als OpenAI ChatGPT der breiten Öffentlichkeit zugänglich machte, konnten nur wenige Menschen, darunter auch die Führungskräfte von OpenAI selbst, mit der Geschwindigkeit der Akzeptanz durch die breite Öffentlichkeit rechnen. Seitdem hat ChatGPT TikTok als schnellste App abgelöst und 100 Millionen Nutzer erreicht. Menschen aus allen Gesellschaftsschichten haben Möglichkeiten gefunden, ChatGPT zur Verbesserung ihrer Effizienz zu nutzen, und Unternehmen haben sich bemüht, Richtlinien für die Verwendung zu entwickeln. Einige Organisationen, darunter viele akademische Einrichtungen, standen der Nutzung überwiegend skeptisch gegenüber, während andere Organisationen wie Technologieunternehmen eine viel liberalere Politik verfolgten und sogar Anwendungen rund um die ChatGPT-API erstellten. Heute werden wir durch die Erstellung einer solchen Anwendung gehen.
Dieser Artikel ist in drei Teile gegliedert: 1) die Erläuterung der der Anwendung zugrunde liegenden Technologien, 2) das Back-End der Anwendung und 3) das Front-End der Anwendung. Wenn Sie grundlegenden Python-Code lesen können, sollten Sie den ersten beiden Abschnitten problemlos folgen können, und wenn Sie über grundlegende Erfahrung mit React.js verfügen, können Sie dem dritten Abschnitt problemlos folgen.
Die Anwendung, die wir heute erstellen, wird für jeden nützlich sein, der regelmäßig Recherchen mit fremdsprachigen Quellen durchführt. Ein gutes Beispiel wären Makroökonomen, die häufig in Fremdsprachen veröffentlichte Regierungsberichte lesen müssen. Manchmal können diese Berichte in maschinelle Übersetzungsdienste kopiert und eingefügt werden, gelegentlich werden sie jedoch in Form von nicht durchsuchbaren PDFs veröffentlicht. In diesen Fällen muss der Forscher menschliche Übersetzer beauftragen, aber Ressourcenbeschränkungen schränken die Anzahl der Berichte, die übersetzt werden können, erheblich ein. Erschwerend kommt hinzu, dass diese Berichte sehr lang und mühsam zu lesen sein können, was die Übersetzung und Analyse kostspielig und zeitaufwändig macht.
Unsere Anwendung wird diesen Prozess vereinfachen, indem sie mehrere uns zur Verfügung stehende KI- und maschinelle Lerntools kombiniert – OCR, maschinelle Übersetzung und große Sprachmodelle. Wir extrahieren den Rohtextinhalt aus einem PDF mithilfe von OCR, übersetzen ihn mithilfe maschineller Übersetzung ins Englische und analysieren die übersetzte Extraktion mithilfe eines großen Sprachmodells.
Für die heutige Bewerbung schauen wir uns eine PDF-Publikation der japanischen Regierung an, das Innovationsweißbuch des Ministeriums für Bildung, Kultur, Sport, Wissenschaft und Technologie. Während die PDF-Datei selbst durchsuchbar ist und in eine Übersetzungsmaschine kopiert werden kann, werden wir so tun, als ob die PDF-Datei nicht durchsuchbar wäre, um die in der Anwendung verwendeten Technologien zu präsentieren. Das Originaldokument finden Sie hier .
Wenn Sie die App jetzt einfach erstellen möchten, können Sie den nächsten Abschnitt gerne überspringen. Wenn Sie jedoch die verschiedenen Technologien, die wir in diesem Artikel verwenden, besser verstehen möchten, erhalten Sie im nächsten Abschnitt einige Hintergrundinformationen.
Die erste Technologie, die wir verwenden werden, ist OCR (Optical Character Recognition), eine der ersten kommerziellen Anwendungen für maschinelles Lernen, die der breiten Öffentlichkeit zugänglich gemacht wurde. OCR-Modelle und -Anwendungen zielen darauf ab, ein Bild oder Bild aufzunehmen und dann Textinformationen aus dem Bild zu identifizieren und zu extrahieren. Dies mag auf den ersten Blick wie eine einfache Aufgabe erscheinen, tatsächlich ist das Problem jedoch recht komplex. Beispielsweise können die Buchstaben leicht verschwommen sein, was eine eindeutige Identifizierung erschwert. Der Buchstabe kann auch in einer ungewöhnlichen Ausrichtung angeordnet sein, was bedeutet, dass das maschinelle Lernmodell vertikalen und auf dem Kopf stehenden Text erkennen muss. Trotz dieser Herausforderungen haben Forscher viele schnelle und leistungsstarke OCR-Modelle entwickelt, von denen viele zu relativ geringen Kosten erhältlich sind. Für die heutige Anwendung verwenden wir das Cloud Vision-Modell von Google, auf das wir über die Google Cloud API zugreifen können.
Die nächste Technologie, die wir verwenden werden, ist die maschinelle Übersetzung. Dies ist, wie auch OCR, ein äußerst schwieriges Problem des maschinellen Lernens. Die menschliche Sprache ist voller Eigenheiten und kontextueller Feinheiten, die es für Computer besonders schwierig machen, sie zu verarbeiten und zu verstehen. Die Übersetzung zwischen unterschiedlichen Sprachpaaren wie Chinesisch und Englisch führt tendenziell zu besonders ungenauen und humorvollen Ergebnissen, da die von Natur aus unterschiedliche Struktur dieser Sprachen sehr unterschiedliche Strategien für die Digitalisierung und Einbettung erfordert. Doch trotz dieser Herausforderungen haben Forscher leistungsstarke und ausgefeilte Modelle entwickelt und sie allgemein verfügbar gemacht. Heute verwenden wir die Übersetzungs-API von Google, eines der besten und am weitesten verbreiteten verfügbaren maschinellen Übersetzungstools.
Die letzte maschinelle Lerntechnologie, die wir verwenden werden, ist das LLM (Large Language Model), das für die künstliche Intelligenz von Verbrauchern revolutionär war. Der LLM ist in der Lage, die Struktur der natürlichen menschlichen Sprache zu verstehen und auf einen großen Datenbestand zurückzugreifen, um eine detaillierte und informative Antwort zu erstellen. Es gibt immer noch viele Einschränkungen der Technologie, aber ihre Flexibilität und Datenverarbeitungsfähigkeiten haben zur Entwicklung vieler neuartiger Techniken für die Auseinandersetzung mit dem Modell inspiriert. Eine solche Technik heißt Prompt Engineering , bei der Benutzer geschickt formulierte und strukturierte Eingaben oder Eingabeaufforderungen für das Modell erstellen und optimieren, um das gewünschte Ergebnis zu erzielen. In der heutigen Anwendung verwenden wir die API von ChatGPT und einige einfache Eingabeaufforderungen, um uns bei der Analyse des übersetzten Berichts zu helfen.
Bevor wir mit der Codierung der Anwendung beginnen, müssen wir uns zunächst für die Dienste anmelden.
Da sich die API-Website von ChatGPT ständig ändert, können wir nicht die genauen Schritte zur Anmeldung für die ChatGPT-API bereitstellen. Auf der API-Dokumentationswebsite sollten Sie jedoch leicht verständliche Anweisungen finden. Fahren Sie einfach fort, bis Sie einen API-Schlüssel erhalten, den wir als ChatGPT-API bezeichnen müssen.
Google Cloud ist etwas komplizierter, aber die Anmeldung ist auch relativ einfach. Gehen Sie einfach zur Google Cloud Console und befolgen Sie die Anweisungen zum Einrichten eines Projekts. Sobald Sie sich im Projekt befinden, möchten Sie zur IAM- und Admin-Konsole navigieren und ein Dienstkonto erstellen. Während sich die Google Cloud Console ständig ändert, sollten Sie in der Benutzeroberfläche navigieren können, indem Sie einfach auf der Webseite nach „IAM“ und „Dienstkonto“ suchen. Sobald das Dienstkonto erstellt ist, möchten Sie die Kopie der privaten Schlüsseldatei auf Ihren Computer herunterladen. Sie sollten auch die Zeichenfolge des privaten Schlüssels kopieren, da die Übersetzungs-REST-API die Zeichenfolge des privaten Schlüssels anstelle der Schlüsseldatei verwendet.
Bevor wir die Einrichtung von Google Cloud abschließen, möchten Sie möglicherweise die Machine Vision API aktivieren, was Sie über die Hauptkonsolenseite tun können. Suchen Sie einfach nach Machine Vision API, klicken Sie bei Google auf das Produkt und aktivieren Sie die API. Sie möchten außerdem einen Bucket erstellen, der die Daten enthält, die wir für dieses Projekt verwenden werden.
Nachdem wir uns nun für die entsprechenden Dienste angemeldet haben, können wir mit der Codierung unserer Anwendung in Python beginnen. Als Erstes möchten wir die erforderlichen Pakete in unserer Python-Umgebung installieren.
Pip install google-cloud-storage google-cloud-vision openai requests
Sobald die Installation abgeschlossen ist, erstellen wir einen neuen Ordner, laden die PDF-Datei herunter und erstellen eine neue Python-Datei im selben Ordner. Wir nennen es document_analyze.py. Wir beginnen mit dem Import der notwendigen Pakete:
import requests Import openai from google.cloud import vision from google.cloud import storage Import os Import json Import time Import shutil
Anschließend können wir einige Grundeinstellungen vornehmen, damit unsere Anwendung die Cloud-Dienste nutzen kann, für die wir uns gerade angemeldet haben:
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = [Path to your Google Cloud key file] openai.api_key = [your openAI API key]
Mit diesen Anmeldeinformationen sollten Sie nun über Ihr Python-Skript auf die Google Cloud- und ChatGPT-APIs zugreifen können. Jetzt können wir die Funktionen schreiben, die die gewünschte Funktionalität für unsere App bereitstellen.
Jetzt können wir mit der Erstellung einiger Funktionen beginnen, die zu den Bausteinen der Anwendung werden. Beginnen wir mit den OCR-Funktionen:
# upload the file to google cloud storage bucket for further processing def upload_file(file_name, bucket_name, bucket_path): client = storage.Client() bucket = client.get_bucket(bucket_name) blob = bucket.blob(bucket_path) blob.upload_from_filename(file_name) # kick off the OCR process on the document. This is asynchronous because OCR can take a while def async_detect_document(gcs_source_uri, gcs_destination_uri): client = vision.ImageAnnotatorClient() input_config = vision.InputConfig(gcs_source=vision.GcsSource(uri=gcs_source_uri), mime_type= 'application/pdf') output_config = vision.OutputConfig( gcs_destination=vision.GcsDestination(uri=gcs_destination_uri), batch_size=100 ) async_request = vision.AsyncAnnotateFileRequest( features=[vision.Feature(type_=vision.Feature.Type.DOCUMENT_TEXT_DETECTION)], input_config=input_config, output_config=output_config ) operation = client.async_batch_annotate_files(requests=[async_request]) def check_results(bucket_path, prefix): storage_client = storage.Client() bucket = storage_client.get_bucket(bucket_path) blob_list = list(bucket.list_blobs(prefix=prefix)) blb = [b for b in blob_list if 'output-' in b.name and '.json' in b.name] return len(blb) != 0 # download the OCR result file def write_to_text(bucket_name, prefix): bucket = storage.Client().get_bucket(bucket_name) blob_list = list(bucket.list_blobs(prefix=prefix)) if not os.path.exists('ocr_results'): os.mkdir('ocr_results') for blob in blob_list: if blob.name.endswith('.json'): with open(os.path.join('ocr_results', blob.name), 'w') as fp_data: fp_data.write(blob.download_as_string().decode('utf-8')) def delete_objects(bucket, prefix): bucket = storage.Client().get_bucket(bucket) blob_list = list(bucket.list_blobs(prefix=prefix)) for blob in blob_list: blob.delete() print('Blob', blob.name, 'Deleted')
Lassen Sie uns im Detail untersuchen, was jede Funktion bewirkt.
Die Funktion upload_file
ist eine Funktion, die einen Bucket aus dem Google Cloud Storage-Container abruft und Ihre Datei dorthin hochlädt. Die hervorragenden Abstraktionen von Google Cloud machen das Schreiben dieser Funktion sehr einfach.
Die Funktion async_detect_document
ruft asynchron die OCR-Funktion von Google Cloud auf. Aufgrund der Vielzahl der in Google Cloud verfügbaren Optionen müssen wir einige Konfigurationsobjekte instanziieren, aber es geht eigentlich nur darum, Google Cloud wissen zu lassen, wo sich die Quelldatei befindet und wohin die Ausgabe geschrieben werden soll. Die Variable batch_size
ist auf 100 gesetzt, sodass Google Cloud das Dokument jeweils mit 100 Seiten verarbeitet. Dadurch wird die Anzahl der zu schreibenden Ausgabedateien reduziert, was die Verarbeitung erleichtert. Ein weiterer wichtiger Punkt ist, dass der Aufruf asynchron erfolgt, was bedeutet, dass die Ausführung des Python-Skripts fortgesetzt wird, anstatt auf den Abschluss der Verarbeitung zu warten. Während dies in dieser Phase keinen großen Unterschied macht, wird es später nützlicher, wenn wir den Python-Code in eine Web-API umwandeln.
Die Funktion check_results
ist eine einfache Cloud-Speicherfunktion, mit der überprüft werden kann, ob die Verarbeitung abgeschlossen ist. Da wir die OCR-Funktion asynchron aufrufen, müssen wir diese Funktion regelmäßig aufrufen, um zu sehen, ob die Ergebnisdatei vorhanden ist. Wenn eine Ergebnisdatei vorhanden ist, gibt die Funktion „true“ zurück und wir können mit der Analyse fortfahren. Wenn keine Ergebnisdatei vorhanden ist, gibt die Funktion „false“ zurück und wir warten weiter, bis die Verarbeitung abgeschlossen ist.
Die Funktion write_to_text
lädt die Ergebnisdatei(en) zur weiteren Verarbeitung auf die Festplatte herunter. Die Funktion durchläuft alle Dateien in Ihrem Bucket mit einem bestimmten Präfix, ruft die Ausgabezeichenfolge ab und schreibt das Ergebnis in das lokale Dateisystem.
Die Funktion delete_objects
ist zwar für die OCR nicht unbedingt relevant, bereinigt jedoch die hochgeladenen Dateien, sodass das System keine unnötigen Artefakte im Google Cloud Storage speichert.
Nachdem wir nun mit den OCR-Aufrufen fertig sind, werfen wir einen Blick auf den maschinellen Übersetzungscode!
Jetzt können wir die Übersetzungsfunktionen definieren:
# detect the language we're translating from def detect_language(text): url = 'https://translation.googleapis.com/language/translate/v2/detect' data = { "q": text, "key": [your google cloud API key] } res = requests.post(url, data=data) return res.json()['data']['detections'][0][0]['language'] # translate the text def translate_text(text): url = 'https://translation.googleapis.com/language/translate/v2' language = detect_language(text) if language == 'en': return text data = { "q": text, "source": language, "target": "en", "format": "text", "key": [your google cloud API key] } res = requests.post(url, data=data) return res.json()['data']['translations'][0]['translatedText']
Diese Funktionen sind ziemlich einfach. Die Funktion detect_language
ruft die Spracherkennungs-API auf, um die Quellsprache für den nachfolgenden Aufruf translate_text
zu ermitteln. Obwohl wir wissen, dass die PDF-Datei auf Japanisch verfasst ist, empfiehlt es sich dennoch, eine Spracherkennung durchzuführen, damit die Anwendung auch andere Sprachen verarbeiten kann. Die Funktion translate_text
verwendet einfach die Google-Übersetzungs-API, um den Text aus der erkannten Quellsprache ins Englische zu übersetzen. Wenn sie jedoch feststellt, dass die Quellsprache bereits Englisch ist, wird die Übersetzung übersprungen.
Zuletzt haben wir die Aufrufe von ChatGPT:
def run_chatgpt_api(report_text): completion = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": ''' Consider the following report: --- %s --- 1. Summarize the purpose of the report. 2. Summarize the primary conclusion of the report. 3. Summarize the secondary conclusion of the report 4. Who is the intended audience for this report? 5. What other additional context would a reader be interested in knowing? Please reply in json format with the keys purpose, primaryConclusion, secondaryConclusion, intendedAudience, and additionalContextString. ''' % report_text}, ] ) return completion.choices[0]['message']['content']
Beachten Sie, dass es sich beim Python-Aufruf um einen relativ einfachen API-Aufruf handelt, die Eingabeaufforderung jedoch so geschrieben ist, dass sie bestimmte Ergebnisse liefert:
Die Eingabeaufforderung stellt den Text des Berichts als Kontext bereit, sodass ChatGPT den Bericht problemlos analysieren kann. Der Text wird durch gestrichelte Linien abgegrenzt, sodass leicht erkennbar ist, wo der Bericht endet und wo die Fragen beginnen.
Die Fragen werden aufgezählt und nicht in einem Absatzformat formuliert. Die Antwort dürfte daher einer ähnlichen Struktur folgen. Durch die Aufzählungsstruktur lässt sich das Ergebnis viel einfacher mit Code analysieren, als wenn ChatGPT im Absatzformat geantwortet hätte.
Die Eingabeaufforderung gibt das Format der Antwort an, in diesem Fall das JSON-Format. Das JSON-Format lässt sich sehr einfach mit Code verarbeiten.
Die Eingabeaufforderung gibt die Schlüssel des JSON-Objekts an und wählt Schlüssel aus, die sehr einfach mit den Fragen verknüpft werden können.
Die Schlüssel verwenden auch eine häufig verwendete Konvention (camelCase), die ChatGPT erkennen sollte. Die JSON-Schlüssel sind vollständige Wörter anstelle von Abkürzungen. Dies erhöht die Wahrscheinlichkeit, dass ChatGPT den tatsächlichen Schlüssel in der Antwort verwendet, da ChatGPT die Angewohnheit hat, im Rahmen seiner Verarbeitung „Rechtschreibkorrekturen“ vorzunehmen.
Der „additionalContextString“ bietet ChatGPT die Möglichkeit, zusätzliche Informationen weiterzugeben. Dies nutzt die Freiformanalysefähigkeit großer Sprachmodelle.
Die Aufforderung verwendet Formulierungen, die häufig in technischen Diskussionen zum jeweiligen Thema zu finden sind. Wie Sie vielleicht anhand der Struktur des API-Aufrufs vermutet haben, besteht das „wahre“ Ziel von ChatGPT nicht unbedingt darin, eine Antwort auf die Eingabeaufforderung bereitzustellen, sondern vielmehr darin, die nächste Zeile in einem Dialog vorherzusagen.
Wenn Ihre Aufforderung daher wie eine Zeile aus einer oberflächlichen Diskussion formuliert ist, erhalten Sie wahrscheinlich eine oberflächliche Antwort. Wenn Ihre Aufforderung jedoch wie eine Zeile aus einer Expertendiskussion formuliert ist, ist die Wahrscheinlichkeit höher, dass Sie ein Expertenergebnis erhalten. Besonders ausgeprägt ist dieser Effekt bei Fächern wie Mathematik oder Technik, ist aber auch hier relevant.
Bei den oben genannten handelt es sich um grundlegende Techniken in einem neuen Werk namens „Prompt Engineering“, bei dem der Benutzer seine Eingabeaufforderungen strukturiert, um ein bestimmtes Ergebnis zu erhalten. Bei großen Sprachmodellen wie ChatGPT kann eine sorgfältige Ausarbeitung der Eingabeaufforderung zu einer dramatischen Steigerung der Effektivität des Modells führen. Es gibt viele Ähnlichkeiten mit dem Codieren, aber Prompt Engineering erfordert viel mehr Intuition und klares Denken seitens des Ingenieurs. Da Tools wie ChatGPT immer stärker in den Arbeitsplatz integriert werden, wird Ingenieurswesen nicht länger ein rein technisches, sondern auch ein philosophisches Unterfangen sein, und Ingenieure sollten darauf achten, ihre Intuition und eine „Theorie des Geistes“ für große Sprachmodelle zu entwickeln, um sie voranzutreiben ihre Effizienz.
Nun lasst uns alles zusammenfügen. Wenn Sie dem vorherigen Abschnitt gefolgt sind, sollte der folgende Codeblock ziemlich einfach zu verstehen sein.
bucket = [Your bucket name] upload_file_name = [the name of the PDF in the Google Cloud bucket] upload_prefix = [the prefix to use for the analysis results] pdf_to_analyze = [path to the PDF to analyze] upload_file(pdf_to_analyze, bucket, upload_file_name) async_detect_document(f'gs://{bucket}/{upload_file_name}', f'gs://{bucket}/{upload_prefix}') while not check_results(bucket, upload_prefix): print('Not done yet... checking again') time.sleep(5) If __name__ == '__main__': write_to_text(bucket, upload_prefix) all_responses = [] for result_json in os.listdir('ocr_results'): with open(os.path.join('ocr_results', result_json)) as fp_res: response = json.load(fp_res) all_responses.extend(response['responses']) texts = [a['fullTextAnnotation']['text'] for a in all_responses] translated_text = [translate_text(t) for t in texts] print('Running cleanup...') delete_objects(bucket, upload_file_name) delete_objects(bucket, upload_prefix) shutil.rmtree('ocr_results') print('Running Analysis...') analysis = run_chatgpt_api('\n'.join(translated_text)) print('=== purpose ====') print(analysis_res['purpose']) print() print('==== primary conclusion =====') print(analysis_res['primaryConclusion']) print() print('==== secondary conclusion =====') print(analysis_res['secondaryConclusion']) print() print('==== intended audience ====') print(analysis_res['intendedAudience']) print() print('===== additional context =====') print(analysis_res['additionalContextString'])
Wir laden den Bericht in den Bucket hoch, starten die OCR und warten, bis die OCR abgeschlossen ist. Anschließend laden wir die OCR-Ergebnisse herunter und fügen sie in eine Liste ein. Wir übersetzen die Ergebnisliste, senden sie zur Analyse an ChatGPT und drucken das Ergebnis der Analyse aus.
Da KI-Tools nicht deterministisch sind, erhalten Sie beim Ausführen dieses Codes wahrscheinlich ein ähnliches, aber nicht identisches Ergebnis. Allerdings habe ich mithilfe der oben verlinkten PDF-Datei folgendes Ergebnis erhalten:
Not done yet... checking again Not done yet... checking again Running cleanup... Blob [pdf name] Deleted Blob [OCR Output Json] Deleted Running Analysis... === purpose ==== The purpose of the report is to analyze the current status and issues of Japan's research capabilities, discuss the government's growth strategy and investment in science and technology, and introduce the initiatives towards realizing a science and technology nation. ==== primary conclusion ===== The primary conclusion of the report is that Japan's research capabilities, as measured by publication index, have been declining internationally, raising concerns about a decline in research capabilities. ==== secondary conclusion ===== The secondary conclusion of the report is that the Kishida Cabinet's growth strategy emphasizes becoming a science and technology nation and strengthening investment in people to achieve growth and distribution. ==== intended audience ==== The intended audience for this report is government officials, policymakers, researchers, and anyone interested in Japan's research capabilities and science and technology policies. ===== additional context ===== The report focuses on the importance of science, technology, and innovation for Japan's future development and highlights the government's efforts to promote a 'new capitalism' based on a virtuous cycle of growth and distribution. It also mentions the revision to the Basic Law on Science, Technology, and Innovation, the 6th Science, Technology, and Innovation Basic Plan, and the concept of Society 5.0 as the future society Japan aims for. The report suggests that comprehensive knowledge is necessary to promote science, technology, and innovation and emphasizes the importance of transcending disciplinary boundaries and utilizing diverse knowledge.
Als Japanischsprecher kann ich bestätigen, dass die Analyse recht gut ist! Sie können selbst experimentieren, indem Sie Ihr eigenes PDF bereitstellen und die Fragen an Ihre Bedürfnisse anpassen. Jetzt verfügen Sie über ein leistungsstarkes KI-Tool zum Übersetzen und Zusammenfassen aller fremdsprachigen PDF-Dateien, auf die Sie stoßen. Wenn Sie zur Zielgruppe dieses Artikels gehören, hoffe ich, dass Sie von den Möglichkeiten, die sich gerade eröffnet haben, ziemlich begeistert sind!
Den vollständigen Backend-Code finden Sie auf meinem GitHub
Wir haben bereits eine leistungsstarke App erstellt, aber in der aktuellen Form muss der Benutzer ein wenig Python beherrschen, um die Anwendung in vollem Umfang nutzen zu können. Was wäre, wenn Sie einen Benutzer hätten, der keinen Code lesen oder schreiben möchte? In diesem Fall möchten wir eine Webanwendung rund um dieses Tool erstellen, damit die Menschen bequem über einen Browser auf die volle Leistungsfähigkeit der KI zugreifen können.
Beginnen wir mit der Erstellung einer React-Anwendung. Stellen Sie sicher, dass Node installiert ist, navigieren Sie zu dem Ordner, in dem der Anwendungscode gespeichert werden soll, führen Sie das Skript „create-react-app“ aus und installieren Sie einige Basispakete:
npx create-react-app llm-frontend
Und dann:
cd llm-frontend npm install bootstrap react-bootstrap axios
Wenn wir eine vollwertige Anwendung entwickeln würden, würden wir auch Pakete installieren wollen, um die Zustandsverwaltung und das Routing zu übernehmen, aber das würde den Rahmen dieses Artikels sprengen. Wir nehmen lediglich Änderungen an der Datei App.jsx vor.
Führen Sie npm run start
aus, um den Entwicklungsserver zu starten, und Ihr Browser sollte eine Seite mit http://localhost:3000 öffnen. Behalten Sie diese Seite und öffnen Sie die Datei App.jsx in Ihrem bevorzugten Texteditor. Sie sollten etwa Folgendes sehen:
import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;
Löschen Sie den Boilerplate-Code und ersetzen Sie ihn durch einige grundlegende Bootstrap-Komponenten.
import React, {useState, useEffect} from 'react'; Import axios from 'axios'; import Container from 'react-bootstrap/Container'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import 'bootstrap/dist/css/bootstrap.min.css'; function App() { return ( <Container> <Row> <Col md={{ span: 10, offset: 1 }}> Main Application Here </Col> </Row> </Container> ); } export default App;
Speichern Sie die App und Sie sollten sehen, dass sie im Browser aktualisiert wird. Es wird jetzt ziemlich spärlich aussehen, aber keine Sorge, wir werden das bald beheben.
Damit diese Anwendung funktioniert, benötigen wir vier Hauptkomponenten: eine Dateiauswahl zum Hochladen der Datei, eine Ergebnisanzeige zur Anzeige der Übersetzung und der Zusammenfassung, eine Texteingabe, damit der Benutzer eigene Fragen stellen kann, und eine Ergebnisanzeige zur Anzeige die Antwort auf die Benutzerfragen.
Wir können vorerst die einfacheren Komponenten ausbauen und Platzhalter für die komplexeren Schnittstellen einfügen. Wenn wir schon dabei sind, erstellen wir die Datencontainer, die wir zur Stromversorgung der Schnittstelle verwenden werden:
import React, {useState, useEffect} from 'react'; import axios from 'axios'; import Container from 'react-bootstrap/Container'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import Button from 'react-bootstrap/Button'; import Accordion from 'react-bootstrap/Accordion'; import Form from 'react-bootstrap/Form'; import ListGroup from 'react-bootstrap/ListGroup'; import 'bootstrap/dist/css/bootstrap.min.css'; const ResultDisplay = ({ initialAnalysis, userQuestion, setUserQuestion, userQuestionResult, userQuestionMessage, userQuestionAsked }) => { return <Row> <Col> <Row style={{marginTop: '10px'}}> <Col md={{ span: 10, offset: 1 }}> <Accordion defaultActiveKey="0"> <Accordion.Item eventKey="0"> <Accordion.Header>Analysis Result</Accordion.Header> <Accordion.Body> {initialAnalysis.analysis} </Accordion.Body> </Accordion.Item> <Accordion.Item eventKey="1"> <Accordion.Header>Raw Translated Text</Accordion.Header> <Accordion.Body> {initialAnalysis.translatedText} </Accordion.Body> </Accordion.Item> <Accordion.Item eventKey="2"> <Accordion.Header>Raw Source Text</Accordion.Header> <Accordion.Body> {initialAnalysis.rawText} </Accordion.Body> </Accordion.Item> </Accordion> </Col> </Row> <Row style={{marginTop: '10px'}}> <Col md={{ span: 8, offset: 1 }}> <Form.Control type="text" placeholder="Additional Questions" value={userQuestion} onChange={e => setUserQuestion(e.target.value)} /> </Col> <Col md={{ span: 2 }}> <Button variant="primary">Ask</Button> </Col> </Row> <Row><Col>{userQuestionMessage}</Col></Row> <Row style={{marginTop: '10px'}}> <Col md={{span: 10, offset: 1}}> {userQuestionResult && userQuestionAsked ? <ListGroup> <ListGroup.Item> <div><b>Q:</b> {userQuestionAsked}</div> <div><b>A:</b> {userQuestionResult}</div></ListGroup.Item> </ListGroup>: ''} </Col> </Row> </Col> </Row> } function App() { const [file, setFile] = useState(null); const [haveFileAnalysisResults, setHaveFileAnalysisResults] = useState(false); const [message, setMessage] = useState(''); const [userQuestionMessage, setUserMessage] = useState(''); const [initialAnalysis, setInitialAnalysis] = useState({analysis: '', translatedText: '', rawText: ''}); const [userQuestion, setUserQuestion] = useState(''); const [userQuestionResult, setUserQuestionResult] = useState(''); const [userQuestionAsked, setUserQuestionAsked] = useState(''); return ( <Container> <Row> <Col md={{ span: 8, offset: 1 }}> <Form.Group controlId="formFile"> <Form.Label>Select a File to Analyze</Form.Label> <Form.Control type="file" onChange={e => setFile(e.target.files[0])} /> </Form.Group> </Col> <Col md={{span: 2}}> <div style={{marginTop: '30px'}}><Button variant="primary" >Analyze</Button></div> </Col> <Col md={12}>{message}</Col> </Row> {haveFileAnalysisResults? <ResultDisplay initialAnalysis={initialAnalysis} userQuestion={userQuestion} setUserQuestion={setUserQuestion} userQuestionResult={userQuestionResult} userQuestionMessage={userQuestionMessage} userQuestionAsked={userQuestionAsked} />: ''} </Container>); }
Es gibt einiges an neuem Code, aber der neue Code ist weder komplex noch bahnbrechend. Der Code ist lediglich eine grundlegende Dateneingabe- und Anzeigeschnittstelle, die auf React-Bootstrap-Komponenten basiert.
Speichern Sie die Datei und Ihr Browser sollte aktualisiert werden, um die Schnittstelle zum Hochladen von Dateien anzuzeigen.
Spielen Sie mit den Variablen herum und Sie sollten eine Schnittstelle wie diese sehen. Dies wird die Frontend-Schnittstelle unserer Anwendung sein.
Nachdem wir nun die grundlegende Frontend-Schnittstelle geschrieben haben, schreiben wir die Funktionen, die die Anwendung mit unserer (noch nicht geschriebenen) API verbinden. Diese Funktionen werden alle im App-Objekt definiert, sodass es Zugriff auf alle React-Hooks hat. Wenn Sie nicht ganz sicher sind, wohin diese Funktionen gehen sollen, können Sie sich den vollständigen Code ansehen, der auf GitHub gehostet wird.
Schreiben wir zunächst ein paar Hilfsfunktionen zum Weiterleiten von Nachrichten an den Benutzer.
const flashMessageBuilder = (setMessage) => (message) => { setMessage(message); setTimeout(() => { setMessage(''); }, (5000)); } const flashMessage = flashMessageBuilder(setMessage); const flashUserQuestionMessage = flashMessageBuilder(setUserQuestionMessage);
Wie Sie sehen, handelt es sich um einfache Funktionen, die eine Nachricht an der entsprechenden Stelle anzeigen und eine Zeit zum Entfernen der Nachricht nach 5 Sekunden erstellen. Dies ist eine einfache UI-Funktion, die die App jedoch viel dynamischer und benutzerfreundlicher macht.
Als nächstes schreiben wir die Funktionen, um die Datei zu analysieren und auf Ergebnisse zu prüfen.
const pollForResults = (batchId) => { flashMessage('Checking for results...'); return new Promise((resolve, reject) => { setTimeout(() => { axios.post('http://localhost:5000/check_if_finished', {batchId}) .then(r => r.data) .then(d => { // the result should have a "status" key and a "result" key. if (d.status === 'complete') { resolve(d); // we're done! } else { resolve(pollForResults(batchId)); // wait 5 seconds and try again. } }).catch(e => reject(e)); }, 5000); }) } const analyzeFile = () => { if (file === null) { flashMessage('No file selected!'); return; } flashMessage('Uploading file...'); const formData = new FormData(); formData.append("file", file); axios.post("http://localhost:5000/analyze_file", formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(r => r.data) .then(d => { // the result should contain a batchId that we use to poll for results. flashMessage('File upload success, waiting for analysis results...'); return pollForResults(d.batchId); }) .then(({analysis, translatedText, rawText}) => { // the result should contain the initial analysis results with the proper format. setInitialAnalysis({analysis, translatedText, rawText}); setHaveFileAnalysisResults(true); // show the results display now that we have results }) .catch(e => { console.log(e); flashMessage('There was an error with the upload. Please check the console for details.'); }) }
Wieder ein ziemlich einfacher Satz von Funktionen. Die Funktion „analysateFile“ sendet die Datei zur Analyse an den Endpunkt „analysate_file“. Die API gibt ihr eine Batch-ID, die sie verwendet, um mit der pollForResults-Funktion nach Ergebnissen zu suchen. Die Funktion pollForResults erreicht den Endpunkt check_if_finished und gibt die Ergebnisse zurück, wenn die Analyse abgeschlossen ist. Sie wartet 5 Sekunden, wenn die Analyse noch verarbeitet wird. Der „analysateFile“-Thread wird dann weiter ausgeführt und die Daten an den entsprechenden Stellen abgelegt.
Zum Schluss schreiben wir die Funktion, mit der der Benutzer Freiformfragen stellen kann:
const askUserQuestion = () => { flashUserQuestionMessage('Asking user question...') axios.post('http://localhost:5000/ask_user_question', { text: initialAnalysis.translatedText, userQuestion }).then(r => r.data) .then(d => { setUserQuestionResult(d.result); setUserQuestionAsked(userQuestion); }).catch(e => { console.log(e); flashUserQuestionMessage('There was an issue asking the question. Please check the console for details'); }); }
Wieder eine ziemlich einfache Funktion. Wir stellen den übersetzten Text zusammen mit der Benutzerfrage bereit, damit unsere API die ChatGPT-Eingabeaufforderung erstellen kann. Das Ergebnis wird dann zur Anzeige in die entsprechenden Datencontainer übertragen.
Wir sind mit der React-App so gut wie fertig, aber bevor wir mit dem Codieren der API beginnen, nehmen wir noch eine kosmetische Änderung vor. Derzeit ist die Anzeige der Analyseergebnisse so konfiguriert, dass die ChatGPT-Analyse als Zeichenfolge angezeigt wird. Allerdings handelt es sich bei der ChatGPT-Analyse tatsächlich um ein JSON-Datenobjekt. Um es also für den menschlichen Gebrauch richtig anzuzeigen, müssen wir dem Anzeigeobjekt einige Formatierungen hinzufügen. Ersetzen Sie das erste Accordion-Element durch den folgenden Code:
<Accordion.Item eventKey="0"> <Accordion.Header>Analysis Result</Accordion.Header> <Accordion.Body> <h6>Purpose</h6> <p>{analysis.purpose}</p> <h6>Primary Conclusion</h6> <p>{analysis.primaryConclusion}</p> <h6>Secondary Conclusion</h6> <p>{analysis.secondaryConclusion}</p> <h6>Intended Audience</h6> <p>{analysis.intendedAudience}</p> <h6>Additional Context</h6> <p>{analysis.additionalContextString}</p> </Accordion.Body> </Accordion.Item>
Nun ist das Frontend fertig, gehen wir zu unserem Python-Code und bauen das Backend.
Zuerst installieren wir Flask, mit dem wir unser Backend schreiben.
Pip install flask flask-cors
Flask ist ein einfaches Framework zum Erstellen von Webanwendungen und Web-APIs. Die Benutzeroberfläche ist unglaublich einfach und das Starten eines Servers ist so einfach wie:
from flask import Flask, request, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route('/') def hello_world(): return "Hello from flask!" if __name__ == '__main__': app.run(debug=True)
Führen Sie diese Datei aus und navigieren Sie in Ihrem Browser zu http://localhost:5000 . Sie sollten die Meldung „Hello from flask!“ sehen. Nachricht.
Jetzt können wir mit dem Aufbau der API-Funktionalität beginnen. Beginnen wir mit dem Importieren der erforderlichen Funktionen und der Definition einiger Konstanten:
from flask import Flask, request, jsonify from flask_cors import CORS import uuid import os import json from document_analyze import upload_file, async_detect_document, check_results, \ write_to_text, translate_text, delete_objects, run_chatgpt_api,\ ask_chatgpt_question app = Flask(__name__) CORS(app) BUCKET = '[YOUR BUCKET NAME]'
Bei diesem Code wird davon ausgegangen, dass sich Ihr Servercode im selben Ordner befindet wie die Datei document_analyze.py, die wir zuvor geschrieben haben. Sie können jedoch eine beliebige Verzeichnisstruktur wählen, solange der Servercode document_analyze.py finden und daraus importieren kann. Schreiben wir den Handler für den Datei-Upload-Endpunkt:
@app.route('/analyze_file', methods=['POST']) def analyze_file(): file_to_analyze = request.files['file'] batch_name = str(uuid.uuid4()) local_file_path = 'uploads/%s.pdf' % batch_name cloud_file_path = '%s.pdf' % batch_name file_to_analyze.save(local_file_path) upload_file(local_file_path, BUCKET, cloud_file_path) async_detect_document( f'gs://{BUCKET}/{cloud_file_path}', f'gs://{BUCKET}/{batch_name}') return jsonify({ 'batchId': batch_name })
Wie Sie sehen, übernimmt diese Funktion die hochgeladene Datei, sendet sie an Google Cloud Storage und startet den OCR-Prozess. Es sollte ziemlich vertraut aussehen, aber hier sind ein paar kleine Änderungen, die es wert sind, erwähnt zu werden. Zunächst wird die Datei durch eine UUID identifiziert, die auch als Stapelname dient. Dies vermeidet potenzielle Kollisionsprobleme, die dadurch entstehen könnten, dass die API mehrmals aufgerufen wird, und identifiziert außerdem alle in einem bestimmten Analysestapel verwendeten Dateien eindeutig, was es einfacher macht, den Fortschritt zu überprüfen und später eine Bereinigung durchzuführen.
Schreiben wir nun den Handler, mit dem die App prüfen kann, ob die Analyse abgeschlossen ist.
@app.route('/check_if_finished', methods=['POST']) def check_if_finished(): batch_name = request.json['batchId'] if not check_results(BUCKET, batch_name): return jsonify({ 'status': 'processing' }) write_to_text(BUCKET, batch_name) all_responses = [] for result_json in os.listdir('ocr_results'): if result_json.endswith('json') and result_json.startswith(batch_name): result_file = os.path.join('ocr_results', result_json) with open(os.path.join('ocr_results', result_json)) as fp_res: response = json.load(fp_res) all_responses.extend(response['responses']) os.remove(result_file) txts = [a['fullTextAnnotation']['text'] for a in all_responses] translated_text = [translate_text(t) for t in txts] print('Running cleanup...') delete_objects(BUCKET, batch_name) os.remove('uploads/%s.pdf' % batch_name) analysis = run_chatgpt_api('\n'.join(translated_text)) analysis_res = json.loads(analysis) return jsonify({ 'status': 'complete', 'analysis': analysis, 'translatedText': translated_text, 'rawText': '\n'.join(txts) })
Auch dies sollte Ihnen recht bekannt vorkommen. Wir prüfen zunächst, ob die OCR abgeschlossen ist, und wenn die OCR nicht abgeschlossen ist, senden wir einfach eine Nachricht zurück, die besagt, dass der Stapel noch verarbeitet wird. Wenn die OCR abgeschlossen ist, setzen wir die Analyse fort, laden die OCR-Ergebnisse herunter und führen die Übersetzungs- und ChatGPT-Pipeline aus. Wir achten auch darauf, die Quelldateien nach Abschluss der Analyse zu bereinigen, um unnötige Speicherkosten zu vermeiden. Wir packen das Ergebnis in das Endergebnisobjekt, das den ChatGPT-Analyse-JSON, den übersetzten Text und den von der OCR extrahierten Rohtext enthält.
Das benutzerdefinierte Fragen-Backend ist zwar eine neue Funktion, aber recht unkompliziert. Zuerst wollen wir die Funktion definieren, um eine benutzerdefinierte Frage zu stellen:
def ask_chatgpt_question(report_text, question_text): completion = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": ''' Consider the following report: --- %s --- Answer the following question: %s ''' % (report_text, question_text)}, ] ) return completion.choices[0]['message']['content']
Jetzt können wir die Funktion importieren und den API-Endpunkt definieren:
@app.route('/ask_user_question') def ask_user_question(): report_text = request.json['text'] user_question = request.json['userQuestion'] response = ask_chatgpt_question(report_text, user_question) return jsonify({ 'result': response })
Nachdem wir nun unsere API geschrieben haben, testen wir sie vom Frontend aus. Fahren Sie fort und laden Sie die Datei über das Frontend hoch. Sie sollten etwa Folgendes sehen:
Warten Sie eine Weile, und das Ergebnis sollte in der WebApp angezeigt werden.
Schauen Sie sich um und Sie werden auch den übersetzten Text sehen:
Und der rohe Quelltext:
Jetzt testen wir die Benutzeroberfläche für benutzerdefinierte Fragen:
Sehr schön! Damit haben wir erfolgreich eine React.js-Anwendung rund um unsere KI-Tools erstellt!
Im heutigen Artikel haben wir eine Anwendung entwickelt, die einige der derzeit leistungsstärksten KI-Tools auf dem Markt nutzt. Während diese spezielle Anwendung auf das Parsen und Zusammenfassen fremdsprachiger PDFs ausgerichtet ist, können ähnliche Techniken angepasst werden, um leistungsstarke, revolutionäre Anwendungen in vielen Bereichen zu entwickeln.
Ich hoffe, dieser Artikel hat Sie dazu inspiriert, eigene KI-gesteuerte Anwendungen zu schreiben. Wenn Sie Kontakt zu mir aufnehmen und mit mir über Ihre Sicht auf KI-Anwendungen sprechen möchten, würde ich mich freuen, von Ihnen zu hören. Wenn Sie eine ähnliche App erstellen möchten, können Sie sich gerne den auf meiner Github- Seite gehosteten Code ansehen, wo Sie Repositorys für das Frontend und das Backend finden
Wenn Sie diese App nutzen möchten, ohne sie selbst erstellen zu müssen, habe ich eine anspruchsvollere Version der Anwendung für den allgemeinen Gebrauch erstellt und gehostet. Wenn Sie Zugriff auf diese Anwendung wünschen, setzen Sie sich bitte mit uns in Verbindung. Wir können dann den Zugriff auf die Website bereitstellen.
Bitte halten Sie Ausschau nach einem Folgeartikel, in dem wir unsere eigene Instanz eines LLM erstellen und ausführen. Sobald der Artikel veröffentlicht ist, werden wir hier einen Link zum Artikel veröffentlichen. Bleiben Sie dran!