paint-brush
Créer un analyseur de documents avec ChatGPT, Google Cloud et Pythonpar@shanglun
9,129 lectures
9,129 lectures

Créer un analyseur de documents avec ChatGPT, Google Cloud et Python

par Shanglun Wang31m2023/08/05
Read on Terminal Reader

Trop long; Pour lire

Nous utilisons ChatGPT, Google Cloud et React JS pour créer une puissante application d'IA qui analyse les documents dans n'importe quelle langue et peut répondre à toute question sur le document. Excellente ressource pour les chercheurs et les codeurs.
featured image - Créer un analyseur de documents avec ChatGPT, Google Cloud et Python
Shanglun Wang HackerNoon profile picture
0-item
1-item

Lorsque OpenAI a lancé ChatGPT au grand public, peu de personnes, y compris les cadres d'OpenAI lui-même, pouvaient anticiper la vitesse d'adoption par le grand public. Depuis lors, ChatGPT a détrôné TikTok en tant qu'application la plus rapide pour atteindre 100 millions d'utilisateurs. Des personnes de tous horizons ont trouvé des moyens d'utiliser ChatGPT pour améliorer leur efficacité, et les entreprises se sont empressées d'élaborer des directives sur son utilisation. Certaines organisations, dont de nombreuses institutions académiques, ont été plutôt sceptiques quant à son utilisation, tandis que d'autres organisations comme les entreprises technologiques ont adopté une politique beaucoup plus libérale, créant même des applications autour de l'API ChatGPT. Aujourd'hui, nous allons parcourir la construction d'une telle application.

Public cible

Cet article est divisé en trois parties : 1) l'explication des technologies sous-jacentes à l'application, 2) le back-end de l'application et 3) le front-end de l'application. Si vous pouvez lire du code Python de base, vous devriez pouvoir suivre facilement les deux premières sections, et si vous avez une expérience de base avec React.js, vous pouvez suivre la troisième section sans problème.

L'application

L'application que nous construisons aujourd'hui sera utile à tous ceux qui effectuent régulièrement des recherches à l'aide de sources en langues étrangères. Un bon exemple serait les macroéconomistes qui doivent souvent lire des rapports gouvernementaux publiés dans des langues étrangères. Parfois, ces rapports peuvent être copiés-collés dans des services de traduction automatique, mais parfois ils sont publiés sous la forme de fichiers PDF non consultables. Dans ces cas, le chercheur devra engager des traducteurs humains, mais les contraintes de ressources limitent considérablement le nombre de rapports pouvant être traduits. Pour aggraver encore le problème, ces rapports peuvent être très longs et fastidieux à lire, ce qui rend la traduction et l'analyse coûteuses et chronophages.


Notre application facilitera ce processus en combinant plusieurs outils d'intelligence artificielle et d'apprentissage automatique à notre disposition - OCR, traduction automatique et grands modèles de langage. Nous extrairons le contenu du texte brut d'un PDF à l'aide de l'OCR, le traduirons en anglais à l'aide de la traduction automatique et analyserons l'extraction traduite à l'aide d'un grand modèle de langage.


Pour l'application d'aujourd'hui, nous examinerons une publication PDF du gouvernement japonais, le livre blanc sur l'innovation du ministère de l'Éducation, de la Culture, des Sports, de la Science et de la Technologie. Alors que le PDF lui-même est consultable et peut être copié dans un moteur de traduction, nous agirons comme si le PDF n'était pas consultable afin de mettre en valeur les technologies utilisées dans l'application. Le document original est disponible ici .


Si vous souhaitez simplement créer l'application maintenant, n'hésitez pas à ignorer la section suivante. Cependant, si vous souhaitez mieux comprendre les différentes technologies que nous utiliserons dans cet article, la section suivante vous donnera un peu de contexte.

Technologies sous-jacentes

La première technologie que nous utiliserons est l'OCR, ou reconnaissance optique de caractères, qui est l'une des premières applications commerciales d'apprentissage automatique à être accessible au grand public. Les modèles et applications OCR visent à prendre une photo ou une image, puis à identifier et extraire des informations textuelles de l'image. Cela peut sembler une tâche simple au premier abord, mais le problème est en fait assez complexe. Par exemple, les lettres peuvent être légèrement floues, ce qui rend difficile une identification positive. La lettre peut également être disposée dans une orientation inhabituelle, ce qui signifie que le modèle d'apprentissage automatique doit identifier le texte vertical et à l'envers. Malgré ces défis, les chercheurs ont développé de nombreux modèles OCR rapides et puissants, et nombre d'entre eux sont disponibles à un coût relativement faible. Pour l'application d'aujourd'hui, nous utiliserons le modèle Cloud Vision de Google, auquel nous pouvons accéder à l'aide de l'API Google Cloud.


La prochaine technologie que nous utiliserons est la traduction automatique. Ceci, comme l'OCR, est un problème d'apprentissage automatique extrêmement difficile. Le langage humain est plein d'idiosyncrasies et de complexités contextuelles qui le rendent particulièrement difficile à traiter et à comprendre pour les ordinateurs. La traduction entre des paires de langues dissemblables comme le chinois et l'anglais a tendance à donner des résultats particulièrement inexacts et humoristiques en raison de la structure intrinsèquement dissemblable de ces langues nécessitant des stratégies très différentes pour la numérisation et l'intégration. Cependant, malgré ces défis, les chercheurs ont développé des modèles puissants et sophistiqués et les ont rendus largement disponibles. Aujourd'hui, nous utiliserons l'API de traduction de Google, l'un des outils de traduction automatique les meilleurs et les plus largement utilisés.


La dernière technologie d'apprentissage automatique que nous utiliserons est le LLM, ou Large Language Model, qui a été révolutionnaire pour l'intelligence artificielle grand public. Le LLM est capable de comprendre la structure du langage humain naturel et est capable de s'appuyer sur un grand nombre de données pour produire une réponse détaillée et informative. Il existe encore de nombreuses limites à la technologie, mais sa flexibilité et ses capacités de traitement des données ont inspiré la création de nombreuses nouvelles techniques pour interagir avec le modèle. L'une de ces techniques s'appelle Prompt Engineering , où les utilisateurs élaborent et modifient des entrées ou des invites habilement formulées et structurées dans le modèle pour obtenir le résultat souhaité. Dans l'application d'aujourd'hui, nous utiliserons l'API de ChatGPT et quelques techniques d'invite simples pour nous aider à analyser le rapport traduit.

Le back-end

Configuration des services cloud

Avant de commencer à coder l'application, nous devrons d'abord nous inscrire aux services.


Étant donné que le site Web de l'API ChatGPT est en constante évolution, nous ne serons pas en mesure de fournir les étapes exactes pour vous inscrire à l'API ChatGPT. Cependant, vous devriez trouver des instructions faciles à suivre sur le site Web de documentation de l'API . Progressez simplement jusqu'à obtenir une clé API, dont nous aurons besoin pour appeler l'API ChatGPT.


Google Cloud est un peu plus compliqué, mais il est également relativement simple de s'inscrire. Rendez-vous simplement sur la console Google Cloud et suivez les instructions pour configurer un projet. Une fois dans le projet, vous souhaiterez accéder à la console IAM & Admin et créer un compte de service. Alors que la console Google Cloud change tout le temps, vous devriez pouvoir naviguer dans l'interface en recherchant simplement "IAM" et le "compte de service" sur la page Web. Une fois le compte de service créé, vous souhaiterez télécharger la copie du fichier de clé privée sur votre ordinateur. Vous voudrez également copier la chaîne de clé privée, car l'API REST de traduction utilise la chaîne de clé privée au lieu du fichier de clé.


Avant de terminer la configuration de Google Cloud, vous souhaiterez activer l'API Machine Vision, ce que vous pouvez faire à partir de la page principale de la console. Recherchez simplement API Machine Vision, cliquez sur le produit de Google et activez l'API. Vous voudrez également créer un compartiment pour contenir les données que nous allons utiliser pour ce projet.

Configuration Python

Maintenant que nous nous sommes inscrits aux services appropriés, nous sommes prêts à commencer à coder notre application en Python. Tout d'abord, nous voudrons installer les packages requis dans notre environnement Python.


Pip install google-cloud-storage google-cloud-vision openai requests


Une fois l'installation terminée, créons un nouveau dossier, téléchargeons le fichier PDF et créons un nouveau fichier Python dans le même dossier. Nous l'appellerons document_analyze.py. Nous commençons par importer les packages nécessaires :


 import requests Import openai from google.cloud import vision from google.cloud import storage Import os Import json Import time Import shutil

Nous pouvons ensuite effectuer une configuration de base afin que notre application puisse utiliser les services cloud auxquels nous venons de nous inscrire :

 os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = [Path to your Google Cloud key file] openai.api_key = [your openAI API key]

Avec ces informations d'identification en place, vous devriez maintenant pouvoir accéder aux API Google Cloud et ChatGPT à partir de votre script Python. Nous pouvons maintenant écrire les fonctions qui fourniront les fonctionnalités souhaitées pour notre application.

Code ROC

Nous pouvons maintenant commencer à créer certaines des fonctions qui deviendront les éléments constitutifs de l'application. Commençons par les fonctions OCR :


 # 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')


Examinons en détail ce que fait chaque fonction.


La fonction upload_file est une fonction qui récupère un compartiment du conteneur Google Cloud Storage et y télécharge votre fichier. Les excellentes abstractions de Google Cloud facilitent l'écriture de cette fonction.


La fonction async_detect_document appelle de manière asynchrone la fonction OCR de Google Cloud. En raison du nombre d'options disponibles dans Google Cloud, nous devons instancier quelques objets de configuration, mais cela permet simplement à Google Cloud de savoir où se trouve le fichier source et où la sortie doit être écrite. La variable batch_size est définie sur 100, donc google cloud traitera le document 100 pages à la fois. Cela réduit le nombre de fichiers de sortie écrits, ce qui facilite le traitement. Une autre chose importante à noter est que l'invocation est asynchrone, ce qui signifie que l'exécution du script Python se poursuivra au lieu d'attendre la fin du traitement. Bien que cela ne fasse pas une grande différence pour cette étape particulière, cela deviendra plus utile plus tard lorsque nous transformerons le code Python en une API Web.


La fonction check_results est une simple fonction de stockage dans le cloud pour vérifier si le traitement est terminé. Étant donné que nous appelons la fonction OCR de manière asynchrone, nous devons appeler cette fonction périodiquement pour voir si le fichier de résultat est présent. S'il existe un fichier de résultats, la fonction renverra true et nous pourrons continuer l'analyse. S'il n'y a pas de fichier de résultat, la fonction renverra faux et nous continuerons d'attendre la fin du traitement.


La fonction write_to_text télécharge le(s) fichier(s) de résultat sur le disque pour un traitement ultérieur. La fonction itérera sur tous les fichiers de votre compartiment avec un préfixe particulier, récupérera la chaîne de sortie et écrira le résultat dans le système de fichiers local.


La fonction delete_objects , bien qu'elle ne soit pas strictement pertinente pour l'OCR, nettoie les fichiers téléchargés afin que le système ne conserve pas d'artefacts inutiles dans Google Cloud Storage.


Maintenant que nous en avons fini avec les invocations OCR, regardons le code de traduction automatique !

Code de traduction automatique

Nous pouvons maintenant définir les fonctions de traduction :


 # 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']


Ces fonctions sont assez simples. La fonction detect_language appelle l'API de détection de langue pour déterminer la langue source de l'appel translate_text suivant. Bien que nous sachions que le PDF est écrit en japonais, il est toujours préférable d'exécuter la détection de langue afin que l'application puisse gérer d'autres langues. La fonction translate_text utilise simplement l'API Google Translation pour traduire le texte de la langue source détectée en anglais, bien que, si elle détermine que la langue source est déjà l'anglais, elle ignorera la traduction.

Ingénierie du code ChatGPT et des invites

Enfin, nous avons les invocations à 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']


Notez que l'appel Python est un appel d'API relativement simple, mais l'invite est écrite de manière à produire des résultats spécifiques :


  1. L'invite fournit le texte du rapport comme contexte, afin que ChatGPT puisse facilement analyser le rapport. Le texte est délimité par des lignes pointillées, ce qui permet de reconnaître facilement où se termine le rapport et où commencent les questions.

  2. Les questions sont énumérées plutôt qu'énoncées sous forme de paragraphe. La réponse est donc susceptible de suivre une structure similaire. La structure énumérée rend le résultat beaucoup plus facile à analyser avec du code que si ChatGPT répondait au format paragraphe.

  3. L'invite spécifie le format de la réponse, dans ce cas le format JSON. Le format JSON est très facile à traiter avec du code.

  4. L'invite spécifie les clés de l'objet JSON et choisit des clés très faciles à associer aux questions.

  5. Les clés utilisent également une convention couramment utilisée (camelCase) que ChatGPT devrait reconnaître. Les clés JSON sont des mots complets au lieu de contractions. Cela rend plus probable que ChatGPT utilise la clé réelle dans la réponse, car ChatGPT a l'habitude de faire des "corrections orthographiques" dans le cadre de son traitement.

  6. Le AdditionalContextString fournit une sortie pour ChatGPT pour transmettre des informations supplémentaires. Cela tire parti de la capacité d'analyse de forme libre des grands modèles de langage.

  7. L'invite utilise une formulation souvent trouvée dans les discussions techniques sur le sujet traité. Comme vous l'avez peut-être deviné à partir de la structure de l'appel d'API, le "vrai" objectif de ChatGPT n'est pas nécessairement de fournir une réponse à l'invite, mais plutôt de prédire la ligne suivante dans un dialogue.

    Par conséquent, si votre invite est formulée comme une ligne d'une discussion au niveau de la surface, vous obtiendrez probablement une réponse au niveau de la surface, alors que si votre invite est formulée comme une ligne d'une discussion d'expert, vous êtes plus susceptible de recevoir un résultat d'expert. Cet effet est particulièrement prononcé pour des matières comme les mathématiques ou la technologie, mais est également pertinent ici.


Ce qui précède sont des techniques de base dans un nouveau corps de travail appelé "ingénierie des invites", où l'utilisateur structure ses invites pour obtenir un résultat spécifique. Avec de grands modèles de langue comme ChatGPT, une conception soignée de l'invite peut entraîner une augmentation spectaculaire de l'efficacité du modèle. Il existe de nombreuses similitudes avec le codage, mais l'ingénierie rapide nécessite beaucoup plus d'intuition et de raisonnement flou de la part de l'ingénieur. Au fur et à mesure que des outils comme ChatGPT deviennent de plus en plus intégrés dans le lieu de travail, l'ingénierie ne sera plus une entreprise purement technique mais aussi philosophique, et les ingénieurs doivent veiller à développer leur intuition et une "théorie de l'esprit" pour les grands modèles de langage afin de stimuler leur efficacité.

Mettre tous ensemble

Maintenant, mettons tout cela ensemble. Si vous avez suivi la section précédente, le bloc de code ci-dessous devrait être assez simple à comprendre.


 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'])


Nous téléchargeons le rapport dans le compartiment, lançons l'OCR et attendons que l'OCR se termine. Nous téléchargeons ensuite les résultats de l'OCR et les mettons dans une liste. Nous traduisons la liste des résultats, l'envoyons à ChatGPT pour analyse et imprimons le résultat de l'analyse.


Parce que les outils d'IA ne sont pas déterministes, si vous exécutez ce code, vous obtiendrez probablement un résultat similaire mais pas identique. Cependant, voici ce que j'ai obtenu en sortie, en utilisant le PDF lié ci-dessus :


 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.


En tant que locuteur japonais, je peux vérifier que l'analyse est plutôt bonne ! Vous pouvez expérimenter par vous-même, en fournissant votre propre PDF et en modifiant les questions en fonction de vos besoins. Vous disposez désormais d'un puissant outil d'intelligence artificielle pour traduire et résumer tous les PDF en langue étrangère que vous rencontrez. Si vous faites partie du public cible de cet article, j'espère que vous vous sentez un peu excité par les possibilités qui viennent de s'ouvrir !


Le code backend complet peut être trouvé sur mon GitHub

L'interface

Nous avons déjà construit une application puissante, mais dans sa forme actuelle, l'utilisateur devra connaître un peu Python pour utiliser l'application dans toute sa mesure. Et si vous aviez un utilisateur qui ne veut ni lire ni écrire de code ? Dans ce cas, nous voudrons créer une application Web autour de cet outil, afin que les gens puissent accéder à toute la puissance de l'IA dans le confort d'un navigateur.

Échafaudage de l'application React

Commençons par créer une application React. Assurez-vous que Node est installé, accédez au dossier dans lequel vous souhaitez que le code de l'application réside, exécutez le script create-react-app et installez quelques packages de base :


npx create-react-app llm-frontend


Et puis:


 cd llm-frontend npm install bootstrap react-bootstrap axios


Si nous développions une application à part entière, nous voudrions également installer des packages pour gérer la gestion de l'état et le routage, mais cela sort du cadre de cet article. Nous allons simplement apporter des modifications au fichier App.jsx.


Exécutez npm run start pour démarrer le serveur de développement et votre navigateur devrait ouvrir une page sur http://localhost:3000 . Conservez cette page et ouvrez le fichier App.jsx dans votre éditeur de texte préféré. Vous devriez voir quelque chose comme ceci :


 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;


Allez-y et supprimez le code passe-partout et remplacez-le par certains composants Bootstrap de base.


 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;


Enregistrez l'application et vous devriez la voir se mettre à jour dans le navigateur. Il semblera assez clairsemé maintenant, mais ne vous inquiétez pas, nous corrigerons cela bientôt.

Composants applicatifs

Pour que cette application fonctionne, nous aurons besoin de quatre composants principaux : un sélecteur de fichier pour télécharger le fichier, un affichage des résultats pour afficher la traduction et le résumé, une saisie de texte pour que l'utilisateur puisse poser ses propres questions et un affichage des résultats pour afficher la réponse aux questions des utilisateurs.


Nous pouvons créer les composants les plus simples et mettre des espaces réservés pour les interfaces les plus complexes pour le moment. Pendant que nous y sommes, créons les conteneurs de données que nous utiliserons pour alimenter l'interface :


 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>); }


Il y a pas mal de nouveau code, mais il n'y a rien de complexe ou de révolutionnaire dans le nouveau code. Le code n'est qu'une interface de saisie et d'affichage de données de base basée sur des composants React-Bootstrap.


Enregistrez le fichier et vous devriez voir la mise à jour de votre navigateur pour afficher l'interface de téléchargement de fichier.

Jouez avec les variables et vous devriez voir une interface comme celle-ci. Ce sera l'interface frontale de notre application.


Maintenant que nous avons écrit l'interface frontale de base, écrivons les fonctions qui connecteront l'application à notre API (pas encore écrite). Ces fonctions seront toutes définies dans l'objet app afin qu'il ait accès à tous les crochets React. Si vous ne savez pas exactement où ces fonctions doivent aller, vous pouvez vous référer au code complet hébergé sur GitHub .


Commençons par écrire quelques fonctions utilitaires pour transmettre des messages à l'utilisateur.


 const flashMessageBuilder = (setMessage) => (message) => { setMessage(message); setTimeout(() => { setMessage(''); }, (5000)); } const flashMessage = flashMessageBuilder(setMessage); const flashUserQuestionMessage = flashMessageBuilder(setUserQuestionMessage);


Comme vous pouvez le voir, ce sont des fonctions simples qui affichent un message à l'endroit approprié et créent un temps pour supprimer le message après 5 secondes. Il s'agit d'une fonctionnalité d'interface utilisateur simple, mais qui rend l'application beaucoup plus dynamique et utilisable.


Ensuite, écrivons les fonctions pour analyser le fichier et vérifier les résultats.


 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.'); }) }


Encore une fois un ensemble assez simple de fonctions. La fonction analyzeFile envoie le fichier au point de terminaison analyze_file pour analyse. L'API lui donnera un ID de lot qu'elle utilisera pour vérifier les résultats avec la fonction pollForResults. La fonction pollForResults atteindra le point de terminaison check_if_finished et renverra les résultats si l'analyse est terminée, et attendra 5 secondes si l'analyse est toujours en cours. Le "thread" analyzeFile continuera alors à s'exécuter, plaçant les données aux endroits appropriés.


Enfin, écrivons la fonction qui permet à l'utilisateur de poser des questions libres :


 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'); }); }


Encore une fois, une fonction assez simple. Nous fournissons le texte traduit avec la question de l'utilisateur afin que notre API puisse construire l'invite ChatGPT. Le résultat est ensuite poussé vers les conteneurs de données appropriés pour l'affichage.


Nous en avons pratiquement terminé avec l'application React, mais avant de passer au codage de l'API, apportons une autre modification cosmétique. À l'heure actuelle, l'affichage des résultats de l'analyse est configuré pour afficher l'analyse ChatGPT sous forme de chaîne. Cependant, l'analyse ChatGPT est en fait un objet de données JSON, donc pour l'afficher correctement pour un usage humain, nous voudrons ajouter une mise en forme à l'objet d'affichage. Remplacez le premier élément Accordion par le code suivant :


 <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>


Maintenant que le frontend est terminé, passons à notre code Python et construisons le backend.

API de flacon

Tout d'abord, installons Flask, que nous utiliserons pour écrire notre backend.


Pip install flask flask-cors


Flask est un cadre simple pour créer des applications Web et des API Web. L'interface est incroyablement simple et faire fonctionner un serveur est aussi simple que :


 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)


Exécutez ce fichier et accédez à http://localhost:5000 dans votre navigateur, et vous devriez voir le message "Hello from flask!" message.


Nous pouvons maintenant commencer à créer la fonctionnalité de l'API. Commençons par importer les fonctions requises et définissons quelques constantes :


 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]'


Ce code suppose que votre code serveur se trouve dans le même dossier que le fichier document_analyze.py que nous avons écrit précédemment, mais vous pouvez choisir la structure de répertoire de votre choix, tant que le code serveur peut trouver et importer à partir de document_analyze.py. Écrivons le gestionnaire pour le point de terminaison de téléchargement de fichier :


 @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 })


Comme vous pouvez le voir, cette fonction prend le fichier téléchargé, l'envoie à Google Cloud Storage et lance le processus d'OCR. Cela devrait sembler assez familier, mais voici quelques petits changements qui méritent d'être soulignés. Tout d'abord, le fichier est identifié par un UUID qui sert également de nom de lot. Cela évite les problèmes de collision potentiels qui pourraient survenir lorsque l'API est appelée plusieurs fois, et identifie également de manière unique tous les fichiers utilisés dans un lot d'analyse particulier, ce qui facilite la vérification de la progression et le nettoyage en cours de ligne.


Écrivons maintenant le gestionnaire qui permet à l'application de vérifier si l'analyse est terminée.


 @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) })


Encore une fois, cela devrait vous sembler assez familier. Nous vérifions d'abord si l'OCR est terminé, et si l'OCR n'est pas fait, nous renvoyons simplement un message indiquant que le lot est toujours en cours de traitement. Si l'OCR est terminé, nous poursuivons l'analyse, en téléchargeant les résultats de l'OCR et en exécutant la traduction et le pipeline ChatGPT. Nous nous assurons également de nettoyer les fichiers sources une fois l'analyse effectuée, afin d'éviter d'engager des frais de stockage inutiles. Nous empaquetons le résultat dans l'objet de résultat final, qui contient le JSON d'analyse ChatGPT, le texte traduit et le texte brut extrait par l'OCR.


Bien que le backend de questions personnalisées soit une nouvelle fonctionnalité, il est assez simple. Nous voudrons d'abord définir la fonction pour poser une question personnalisée :


 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']


Nous pouvons maintenant importer la fonction et définir le point de terminaison de l'API :


 @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 })


Maintenant que nous avons écrit notre API, testons-la depuis le frontend. Allez-y et téléchargez le fichier via le frontend. Vous devriez voir quelque chose comme ceci :

Après avoir téléchargé le fichier et cliqué sur le bouton Analyser


Attendez un peu et vous devriez voir le résultat apparaître sur la WebApp.

Le résultat de base

Regardez autour de vous et vous verrez également le texte traduit :

Texte traduit

Et le texte source brut :

Texte brut

Testons maintenant l'interface des questions personnalisées :

Poser une question à propos du document

Pas mal! Avec cela, nous avons construit avec succès une application React.js autour de nos outils d'IA !

Conclusion

Dans l'article d'aujourd'hui, nous avons créé une application qui exploite certains des outils d'IA les plus puissants actuellement sur le marché. Bien que cette application spécifique soit orientée vers l'analyse et la synthèse de fichiers PDF en langue étrangère, des techniques similaires peuvent être adaptées pour développer de puissantes applications révolutionnaires dans de nombreux domaines.


J'espère que cet article vous a inspiré pour écrire vos propres applications pilotées par l'IA. Si vous souhaitez me contacter et me parler de votre point de vue sur les applications d'IA, j'aimerais avoir de vos nouvelles. Si vous cherchez à créer une application similaire, n'hésitez pas à vous référer au code hébergé sur ma page Github où vous pouvez trouver des référentiels pour le Frontend et le Backend


Si vous souhaitez utiliser cette application sans avoir à la créer vous-même, j'ai créé et hébergé une version plus sophistiquée de l'application à usage général. Si vous souhaitez accéder à cette application, veuillez nous contacter et nous pourrons fournir l'accès au site Web.


Veuillez être à l'affût d'un article de suivi, où nous construisons et exécutons notre propre instance d'un LLM. Lorsque l'article sera publié, nous publierons un lien vers l'article ici. Restez à l'écoute!