Cuando OpenAI lanzó ChatGPT al público en general, pocas personas, incluidos los ejecutivos de OpenAI, podían anticipar la velocidad de adopción por parte del público en general. Desde entonces, ChatGPT ha desbancado a TikTok como la aplicación más rápida en alcanzar los 100 millones de usuarios. Personas de todos los ámbitos de la vida han encontrado formas de usar ChatGPT para mejorar su eficiencia, y las empresas se han apresurado a desarrollar pautas sobre su uso. Algunas organizaciones, incluidas muchas instituciones académicas, se han mostrado escépticas sobre su uso, mientras que otras organizaciones, como las empresas de tecnología, han adoptado una política mucho más liberal, incluso creando aplicaciones en torno a la API de ChatGPT. Hoy, analizaremos la creación de una de esas aplicaciones.
Este artículo se divide en tres partes: 1) la explicación de las tecnologías subyacentes a la aplicación, 2) el back-end de la aplicación y 3) el front-end de la aplicación. Si puede leer algo de código básico de Python, debería poder seguir las dos primeras secciones fácilmente, y si tiene alguna experiencia básica con React.js, puede seguir la tercera sección sin problemas.
La aplicación que estamos construyendo hoy será útil para cualquier persona que realice investigaciones regularmente utilizando fuentes de idiomas extranjeros. Un buen ejemplo serían los macroeconomistas que a menudo tienen que leer informes gubernamentales publicados en idiomas extranjeros. A veces, estos informes se pueden copiar y pegar en los servicios de traducción automática, pero ocasionalmente se publican en forma de archivos PDF sin capacidad de búsqueda. En esos casos, el investigador deberá contratar traductores humanos, pero las limitaciones de recursos limitan significativamente la cantidad de informes que se pueden traducir. Para complicar aún más el problema, estos informes pueden ser muy largos y tediosos de leer, lo que hace que la traducción y el análisis sean costosos y consuman mucho tiempo.
Nuestra aplicación facilitará este proceso al combinar varias herramientas de inteligencia artificial y aprendizaje automático a nuestra disposición: OCR, traducción automática y modelos de idiomas grandes. Extraeremos el contenido de texto sin procesar de un PDF usando OCR, lo traduciremos al inglés usando traducción automática y analizaremos la extracción traducida usando un modelo de lenguaje grande.
Para la aplicación de hoy, veremos una publicación en PDF del gobierno japonés, el Libro Blanco de Innovación del Ministerio de Educación, Cultura, Deportes, Ciencia y Tecnología. Si bien el PDF en sí se puede buscar y se puede copiar en un motor de traducción, actuaremos como si el PDF no se pudiera buscar para mostrar las tecnologías utilizadas en la aplicación. El documento original se puede encontrar aquí .
Si solo desea crear la aplicación ahora, no dude en omitir la siguiente sección. Sin embargo, si desea obtener una mejor comprensión de las diversas tecnologías que usaremos en este artículo, la siguiente sección le brindará algunos antecedentes.
La primera tecnología que utilizaremos es OCR, o reconocimiento óptico de caracteres, que es una de las primeras aplicaciones comerciales de aprendizaje automático que estará disponible para el público en general. Los modelos y aplicaciones de OCR tienen como objetivo tomar una foto o imagen y luego identificar y extraer información textual de la imagen. Esto puede parecer una tarea sencilla al principio, pero en realidad el problema es bastante complejo. Por ejemplo, las letras pueden estar un poco borrosas, lo que dificulta una identificación positiva. La letra también puede estar dispuesta en una orientación inusual, lo que significa que el modelo de aprendizaje automático tiene que identificar el texto vertical y al revés. A pesar de estos desafíos, los investigadores han desarrollado muchos modelos de OCR rápidos y potentes, y muchos de ellos están disponibles a un costo relativamente bajo. Para la aplicación de hoy, utilizaremos el modelo Cloud Vision de Google, al que podemos acceder mediante la API de Google Cloud.
La siguiente tecnología que utilizaremos es la traducción automática. Esto, como OCR, es un problema de aprendizaje automático extremadamente difícil. El lenguaje humano está lleno de idiosincrasias y complejidades contextuales que lo hacen especialmente difícil de procesar y comprender para las computadoras. La traducción entre pares de idiomas diferentes, como el chino y el inglés, tiende a producir resultados particularmente inexactos y humorísticos debido a la estructura inherentemente diferente de estos idiomas que requieren estrategias muy diferentes para la digitalización y la incrustación. Sin embargo, a pesar de estos desafíos, los investigadores han desarrollado modelos potentes y sofisticados y los han puesto a disposición del público en general. Hoy usaremos la API de traducción de Google, una de las mejores y más utilizadas herramientas de traducción automática disponibles.
La última tecnología de aprendizaje automático que utilizaremos es LLM, o Large Language Model, que ha sido revolucionaria para la inteligencia artificial del consumidor. El LLM puede comprender la estructura del lenguaje humano natural y puede aprovechar una gran cantidad de datos para producir una respuesta detallada e informativa. Todavía existen muchas limitaciones en la tecnología, pero su flexibilidad y capacidades de procesamiento de datos han inspirado la creación de muchas técnicas novedosas para interactuar con el modelo. Una de estas técnicas se llama Ingeniería de avisos , en la que los usuarios elaboran y ajustan entradas o avisos hábilmente redactados y estructurados en el modelo para obtener el resultado deseado. En la aplicación de hoy, utilizaremos la API de ChatGPT y algunos sencillos pasos de ingeniería para ayudarnos a analizar el informe traducido.
Antes de comenzar a codificar la aplicación, primero debemos registrarnos en los servicios.
Debido a que el sitio web de la API de ChatGPT siempre está cambiando, no podremos proporcionar los pasos exactos para registrarse en la API de ChatGPT. Sin embargo, debe encontrar instrucciones fáciles de seguir en el sitio web de documentación de la API . Simplemente progrese hasta que obtenga una clave de API, que necesitaremos para llamar a la API de ChatGPT.
Google Cloud es un poco más complicado, pero también es relativamente simple registrarse. Simplemente diríjase a la consola de Google Cloud y siga las instrucciones para configurar un proyecto. Una vez en el proyecto, querrá navegar a la consola de administración e IAM y crear una cuenta de servicio. Si bien la consola de Google Cloud cambia todo el tiempo, debería poder navegar por la interfaz simplemente buscando "IAM" y la "Cuenta de servicio" en la página web. Una vez que se crea la cuenta de servicio, querrá descargar la copia del archivo de clave privada a su computadora. También querrá copiar la cadena de clave privada, ya que la API REST de traducción usa la cadena de clave privada en lugar del archivo de clave.
Antes de finalizar la configuración de Google Cloud, querrá habilitar la API de Machine Vision, lo que puede hacer desde la página principal de la consola. Simplemente busque la API de Machine Vision, haga clic en el producto de Google y active la API. También querrá crear un depósito para almacenar los datos que vamos a usar para este proyecto.
Ahora que nos hemos registrado en los servicios adecuados, estamos listos para comenzar a codificar nuestra aplicación en Python. Lo primero es lo primero, querremos instalar los paquetes necesarios en nuestro entorno de Python.
Pip install google-cloud-storage google-cloud-vision openai requests
Una vez que se complete la instalación, creemos una nueva carpeta, descarguemos el archivo PDF y creemos un nuevo archivo de Python en la misma carpeta. Lo llamaremos document_analyze.py. Empezamos importando los paquetes necesarios:
import requests Import openai from google.cloud import vision from google.cloud import storage Import os Import json Import time Import shutil
Luego podemos hacer una configuración básica para que nuestra aplicación pueda usar los servicios en la nube para los que nos acabamos de registrar:
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = [Path to your Google Cloud key file] openai.api_key = [your openAI API key]
Con estas credenciales en su lugar, ahora debería poder acceder a las API de Google Cloud y ChatGPT desde su secuencia de comandos de Python. Ahora podemos escribir las funciones que proporcionarán la funcionalidad deseada para nuestra aplicación.
Ahora podemos comenzar a construir algunas de las funciones que se convertirán en los componentes básicos de la aplicación. Comencemos con las funciones de 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')
Examinemos en detalle qué hace cada función.
La función upload_file
es una función que toma un cubo del contenedor de Google Cloud Storage y carga su archivo en él. Las excelentes abstracciones de Google Cloud hacen que sea muy fácil escribir esta función.
La función async_detect_document
invoca de forma asíncrona la función OCR de Google Cloud. Debido a la cantidad de opciones disponibles en Google Cloud, tenemos que crear instancias de algunos objetos de configuración, pero en realidad solo permite que Google Cloud sepa dónde está el archivo de origen y dónde debe escribirse la salida. La variable batch_size
se establece en 100, por lo que Google Cloud procesará el documento 100 páginas a la vez. Esto reduce la cantidad de archivos de salida que se escriben, lo que facilita el procesamiento. Otra cosa importante a tener en cuenta es que la invocación es asíncrona, lo que significa que la ejecución del script de Python continuará en lugar de esperar a que finalice el procesamiento. Si bien esto no hace una gran diferencia en esta etapa en particular, será más útil más adelante cuando transformemos el código de Python en una API web.
La función check_results
es una función simple de almacenamiento en la nube para verificar si se realizó el procesamiento. Debido a que estamos invocando la función OCR de forma asíncrona, debemos llamar a esta función periódicamente para ver si el archivo de resultados está presente. Si hay un archivo de resultados, la función devolverá verdadero y podemos continuar con el análisis. Si no hay un archivo de resultados, la función devolverá falso y seguiremos esperando hasta que finalice el procesamiento.
La función write_to_text
descarga los archivos de resultados en el disco para su posterior procesamiento. La función iterará sobre todos los archivos en su depósito con un prefijo particular, recuperará la cadena de salida y escribirá el resultado en el sistema de archivos local.
La función delete_objects
, aunque no es estrictamente relevante para el OCR, limpia los archivos cargados para que el sistema no guarde artefactos innecesarios en Google Cloud Storage.
Ahora que hemos terminado con las invocaciones de OCR, ¡veamos el código de traducción automática!
Ahora podemos definir las funciones de traducción:
# 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']
Estas funciones son bastante sencillas. La función detect_language
llama a la API de detección de idioma para determinar el idioma de origen para la siguiente llamada translate_text
. Si bien sabemos que el PDF está escrito en japonés, aún es una buena práctica ejecutar la detección de idioma para que la aplicación pueda manejar otros idiomas. La función translate_text
simplemente usa la API de traducción de Google para traducir el texto del idioma de origen detectado al inglés, aunque, si determina que el idioma de origen ya es inglés, omitirá la traducción.
Por último, tenemos las invocaciones a 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']
Tenga en cuenta que la llamada de Python es una llamada de API relativamente simple, pero el indicador está escrito de manera que produzca resultados específicos:
El indicador proporciona el texto del informe como contexto, por lo que ChatGPT puede analizar el informe fácilmente. El texto está delimitado con líneas discontinuas, lo que facilita reconocer dónde termina el informe y dónde comienzan las preguntas.
Las preguntas se enumeran en lugar de establecerse en un formato de párrafo. Por lo tanto, es probable que la respuesta siga una estructura similar. La estructura enumerada hace que el resultado sea mucho más fácil de analizar con código que si ChatGPT respondiera en formato de párrafo.
El indicador especifica el formato de la respuesta, en este caso, el formato JSON. El formato JSON es muy fácil de procesar con código.
El indicador especifica las claves del objeto JSON y elige claves que son muy fáciles de asociar con las preguntas.
Las claves también usan una convención de uso común (camelCase) que ChatGPT debería reconocer. Las claves JSON son palabras completas en lugar de contracciones. Esto hace que sea más probable que ChatGPT use la clave real en la respuesta, ya que ChatGPT tiene la costumbre de hacer "correcciones ortográficas" como parte de su procesamiento.
La cadena de contexto adicional proporciona una salida para que ChatGPT pase información adicional. Esto aprovecha la capacidad de análisis de forma libre de los grandes modelos de lenguaje.
El mensaje utiliza frases que se encuentran a menudo en discusiones técnicas sobre el tema en cuestión. Como puede haber deducido de la estructura de la llamada a la API, el objetivo "verdadero" de ChatGPT no es necesariamente proporcionar una respuesta al aviso, sino predecir la siguiente línea en un diálogo.
Por lo tanto, si su mensaje está redactado como una línea de una discusión de nivel superficial, es probable que obtenga una respuesta de nivel superficial, mientras que si su mensaje está redactado como una línea de una discusión de expertos, es más probable que reciba un resultado experto. Este efecto es especialmente pronunciado para materias como matemáticas o tecnología, pero aquí también es relevante.
Las anteriores son técnicas básicas en un nuevo cuerpo de trabajo llamado "ingeniería de avisos", donde el usuario estructura sus avisos para obtener un resultado específico. Con modelos de lenguaje grandes como ChatGPT, la elaboración cuidadosa del indicador puede resultar en aumentos dramáticos en la efectividad del modelo. Hay muchas similitudes con la codificación, pero la ingeniería rápida requiere mucha más intuición y razonamiento confuso por parte del ingeniero. A medida que herramientas como ChatGPT se integran cada vez más en el lugar de trabajo, la ingeniería ya no será un esfuerzo puramente técnico sino también filosófico, y los ingenieros deben tener cuidado de desarrollar su intuición y una "teoría de la mente" para modelos de lenguaje grandes a fin de impulsar su eficiencia.
Ahora vamos a ponerlo todo junto. Si siguió la sección anterior, el siguiente bloque de código debería ser bastante sencillo de entender.
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'])
Subimos el informe al depósito, iniciamos el OCR y esperamos a que finalice. Luego descargamos los resultados de OCR y los ponemos en una lista. Traducimos la lista de resultados, la enviamos a ChatGPT para su análisis e imprimimos el resultado del análisis.
Debido a que las herramientas de IA no son deterministas, si ejecuta este código, probablemente obtendrá un resultado similar pero no idéntico. Sin embargo, esto es lo que obtuve como resultado, usando el PDF vinculado anteriormente:
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.
¡Como hablante de japonés, puedo verificar que el análisis es bastante bueno! Puede experimentar por su cuenta, proporcionando su propio PDF y cambiando las preguntas según sus necesidades. Ahora tiene una poderosa herramienta de IA para traducir y resumir cualquier PDF en idioma extranjero que encuentre. Si usted está en el público objetivo de este artículo, ¡espero que se sienta un poco emocionado por las posibilidades que se acaban de abrir!
El código backend completo se puede encontrar en mi GitHub
Ya creamos una aplicación poderosa, pero en su forma actual, el usuario tendrá que saber un poco de Python para usar la aplicación en toda su extensión. ¿Qué sucede si tiene un usuario que no quiere leer ni escribir ningún código? En ese caso, querremos crear una aplicación web en torno a esta herramienta, para que las personas puedan acceder a todo el poder de la IA desde la comodidad de un navegador.
Comencemos por crear una aplicación React. Asegúrese de que Node esté instalado, navegue a la carpeta donde desea que viva el código de la aplicación, ejecute el script create-react-app e instale algunos paquetes básicos:
npx create-react-app llm-frontend
Y luego:
cd llm-frontend npm install bootstrap react-bootstrap axios
Si estuviéramos desarrollando una aplicación completa, también querríamos instalar paquetes para manejar la gestión de estado y el enrutamiento, pero eso está fuera del alcance de este artículo. Simplemente realizaremos ediciones en el archivo App.jsx.
Ejecute npm run start
para iniciar el servidor de desarrollo y su navegador debería abrir una página en http://localhost:3000 . Mantenga esa página y abra el archivo App.jsx en su editor de texto favorito. Debería ver algo como esto:
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;
Adelante, elimine el código repetitivo y reemplácelo con algunos componentes básicos de Bootstrap.
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;
Guarde la aplicación y debería verla actualizada en el navegador. Se verá bastante escaso ahora, pero no se preocupe, lo arreglaremos pronto.
Para que esta aplicación funcione, necesitaremos cuatro componentes principales: un selector de archivos para cargar el archivo, una pantalla de resultados para mostrar la traducción y el resumen, una entrada de texto para que el usuario pueda hacer sus propias preguntas y una pantalla de resultados para mostrar la respuesta a las preguntas de los usuarios.
Podemos construir los componentes más simples y colocar marcadores de posición para las interfaces más complejas por ahora. Mientras estamos en eso, creemos los contenedores de datos que usaremos para potenciar la interfaz:
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>); }
Hay bastante código nuevo, pero no hay nada complejo o innovador en el nuevo código. El código es solo una interfaz básica de entrada y visualización de datos basada en componentes React-Bootstrap.
Guarde el archivo y debería ver la actualización de su navegador para mostrar la interfaz de carga de archivos.
Juega con las variables y deberías ver una interfaz como esta. Esta será la interfaz frontend de nuestra aplicación.
Ahora que hemos escrito la interfaz frontend básica, escribamos las funciones que conectarán la aplicación a nuestra API (aún no escrita). Todas estas funciones se definirán en el objeto de la aplicación, por lo que tendrá acceso a todos los ganchos de React. Si no está completamente seguro de dónde deben ir estas funciones, puede consultar el código completo alojado en GitHub .
Primero, escribamos un par de funciones de utilidad para pasar mensajes al usuario.
const flashMessageBuilder = (setMessage) => (message) => { setMessage(message); setTimeout(() => { setMessage(''); }, (5000)); } const flashMessage = flashMessageBuilder(setMessage); const flashUserQuestionMessage = flashMessageBuilder(setUserQuestionMessage);
Como puede ver, estas son funciones simples que muestran un mensaje en el lugar apropiado y crean un tiempo para eliminar el mensaje después de 5 segundos. Esta es una función de interfaz de usuario simple, pero hace que la aplicación se sienta mucho más dinámica y útil.
A continuación, escribamos las funciones para analizar el archivo y comprobar los resultados.
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.'); }) }
Una vez más, un conjunto bastante simple de funciones. La función AnalyzeFile envía el archivo al extremo de Analyze_file para su análisis. La API le dará un ID de lote que utiliza para verificar los resultados con la función pollForResults. La función pollForResults llegará al punto final check_if_finished y devolverá los resultados si el análisis finalizó, y esperará 5 segundos si el análisis aún se está procesando. El "hilo" de AnalyzeFile continuará ejecutándose, poniendo los datos en los lugares apropiados.
Por último, escribamos la función que permite al usuario hacer preguntas de forma libre:
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'); }); }
Nuevamente, una función bastante simple. Proporcionamos el texto traducido junto con la pregunta del usuario para que nuestra API pueda construir el aviso de ChatGPT. Luego, el resultado se envía a los contenedores de datos apropiados para su visualización.
Casi hemos terminado con la aplicación React, pero antes de pasar a codificar la API, hagamos un cambio cosmético más. En este momento, la pantalla de resultados del análisis está configurada para mostrar el análisis de ChatGPT como una cadena. Sin embargo, el análisis de ChatGPT es en realidad un objeto de datos JSON, por lo que para mostrarlo correctamente para uso humano, querremos agregar algún formato al objeto de visualización. Reemplace el primer elemento de acordeón con el siguiente código:
<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>
Ahora que el frontend está listo, vayamos a nuestro código Python y construyamos el backend.
Primero, instalemos Flask, que usaremos para escribir nuestro backend.
Pip install flask flask-cors
Flask es un marco simple para crear aplicaciones web y API web. La interfaz es increíblemente simple y hacer que un servidor funcione es tan fácil como:
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)
Ejecute este archivo y navegue a http://localhost:5000 en su navegador, y debería ver el mensaje "¡Hola desde el matraz!" mensaje.
Ahora podemos comenzar a construir la funcionalidad de la API. Comencemos importando las funciones requeridas y definiendo algunas 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]'
Este código asume que el código de su servidor está en la misma carpeta que el archivo document_analyze.py que escribimos anteriormente, pero puede elegir cualquier estructura de directorio que desee, siempre que el código del servidor pueda buscar e importar desde document_analyze.py. Escribamos el controlador para el punto final de carga de archivos:
@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 })
Como puede ver, esta función toma el archivo cargado, lo envía a Google Cloud Storage e inicia el proceso de OCR. Debería parecer bastante familiar, pero aquí hay un par de pequeños cambios que vale la pena señalar. En primer lugar, el archivo se identifica mediante un UUID que también sirve como nombre del lote. Esto evita posibles problemas de colisión que podrían surgir si se llama a la API varias veces, y también identifica de forma única todos los archivos utilizados en un lote de análisis en particular, lo que facilita la verificación del progreso y la limpieza en el futuro.
Ahora escribamos el controlador que permite que la aplicación verifique si el análisis ha finalizado.
@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) })
Una vez más, esto debería parecer bastante familiar. Primero comprobamos si se ha realizado el OCR y, si no se ha realizado, simplemente devolvemos un mensaje que indica que el lote aún se está procesando. Si se realiza el OCR, continuamos el análisis, descargamos los resultados del OCR y ejecutamos la traducción y la canalización de ChatGPT. También nos aseguramos de limpiar los archivos de origen una vez que se realiza el análisis, para evitar incurrir en costos de almacenamiento innecesarios. Empaquetamos el resultado en el objeto de resultado final, que contiene el JSON de análisis de ChatGPT, el texto traducido y el texto sin procesar extraído por el OCR.
Si bien el backend de preguntas personalizadas es una característica nueva, es bastante sencillo. Primero querremos definir la función para hacer una pregunta personalizada:
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']
Ahora podemos importar la función y definir el punto final de la 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 })
Ahora que hemos escrito nuestra API, vamos a probarla desde la interfaz. Continúe y cargue el archivo a través de la interfaz. Debería ver algo como esto:
Espere un poco y debería ver el resultado en la aplicación web.
Mire a su alrededor y también verá el texto traducido:
Y el texto fuente sin procesar:
Ahora probemos la interfaz de preguntas personalizadas:
¡Bastante agradable! ¡Con eso, hemos creado con éxito una aplicación React.js en torno a nuestras herramientas de IA!
En el artículo de hoy, creamos una aplicación que aprovecha algunas de las herramientas de inteligencia artificial más poderosas que existen actualmente en el mercado. Si bien esta aplicación específica está orientada a analizar y resumir archivos PDF en idiomas extranjeros, se pueden adaptar técnicas similares para desarrollar poderosas aplicaciones revolucionarias en muchos campos.
Espero que este artículo lo haya inspirado a escribir sus propias aplicaciones impulsadas por IA. Si desea comunicarse conmigo y hablar sobre su perspectiva sobre las aplicaciones de IA, me encantaría saber de usted. Si está buscando crear una aplicación similar, no dude en consultar el código alojado en mi página de Github , donde puede encontrar repositorios para el Frontend y el Backend .
Si desea utilizar esta aplicación sin tener que crearla por su cuenta, he creado y alojado una versión más sofisticada de la aplicación para uso general. Si desea acceder a esta aplicación, comuníquese con nosotros y podemos brindarle acceso al sitio web.
Esté atento a un artículo de seguimiento, donde construimos y ejecutamos nuestra propia instancia de un LLM. Cuando se publique el artículo, publicaremos un enlace al artículo aquí. ¡Manténganse al tanto!