Quando a OpenAI lançou o ChatGPT para o público em geral, poucas pessoas, incluindo os executivos da própria OpenAI, puderam prever a velocidade de adoção pelo público em geral. Desde então, o ChatGPT destronou o TikTok como o aplicativo mais rápido a atingir 100 milhões de usuários. Pessoas de todas as esferas da vida encontraram maneiras de usar o ChatGPT para melhorar sua eficiência, e as empresas se esforçaram para desenvolver diretrizes sobre seu uso. Algumas organizações, incluindo muitas instituições acadêmicas, têm sido céticas sobre seu uso, enquanto outras organizações, como empresas de tecnologia, adotaram uma política muito mais liberal, até mesmo criando aplicativos em torno da API ChatGPT. Hoje, vamos percorrer a construção de um desses aplicativos.
Este artigo está dividido em três partes: 1) a explicação das tecnologias subjacentes ao aplicativo, 2) o back-end do aplicativo e 3) o front-end do aplicativo. Se você puder ler algum código Python básico, poderá seguir as duas primeiras seções facilmente e, se tiver alguma experiência básica com React.js, poderá seguir a terceira seção sem problemas.
O aplicativo que estamos construindo hoje será útil para qualquer pessoa que faça pesquisas regularmente usando fontes em idiomas estrangeiros. Um bom exemplo seriam os macroeconomistas que muitas vezes precisam ler relatórios do governo publicados em línguas estrangeiras. Às vezes, esses relatórios podem ser copiados e colados em serviços de tradução automática, mas ocasionalmente são publicados na forma de PDFs não pesquisáveis. Nesses casos, o pesquisador precisará contratar tradutores humanos, mas as restrições de recursos limitam significativamente o número de relatórios que podem ser traduzidos. Para agravar ainda mais o problema, esses relatórios podem ser muito longos e tediosos de ler, o que torna a tradução e a análise caras e demoradas.
Nosso aplicativo facilitará esse processo ao combinar várias ferramentas de IA e aprendizado de máquina à nossa disposição - OCR, tradução automática e modelos de linguagem grandes. Vamos extrair o conteúdo de texto bruto de um PDF usando OCR, traduzi-lo para o inglês usando tradução automática e analisar a extração traduzida usando um modelo de idioma grande.
Para a inscrição de hoje, veremos uma publicação em PDF do governo japonês, o Livro Branco de Inovação do Ministério da Educação, Cultura, Esportes, Ciência e Tecnologia. Embora o próprio PDF seja pesquisável e possa ser copiado para um mecanismo de tradução, agiremos como se o PDF não fosse pesquisável para mostrar as tecnologias usadas no aplicativo. O documento original pode ser encontrado aqui .
Se você deseja apenas criar o aplicativo agora, sinta-se à vontade para pular a próxima seção. No entanto, se você quiser entender melhor as várias tecnologias que usaremos neste artigo, a próxima seção fornecerá algumas informações básicas.
A primeira tecnologia que usaremos é OCR, ou reconhecimento óptico de caracteres, que é um dos primeiros aplicativos comerciais de aprendizado de máquina a se tornar disponível ao público em geral. Os modelos e aplicativos de OCR visam tirar uma foto ou imagem e, em seguida, identificar e extrair informações textuais da imagem. A princípio pode parecer uma tarefa simples, mas na verdade o problema é bastante complexo. Por exemplo, as letras podem estar um pouco borradas, dificultando uma identificação positiva. A carta também pode ser organizada em uma orientação incomum, o que significa que o modelo de aprendizado de máquina precisa identificar o texto vertical e de cabeça para baixo. Apesar desses desafios, os pesquisadores desenvolveram muitos modelos de OCR rápidos e poderosos, e muitos deles estão disponíveis a um custo relativamente baixo. Para o aplicativo de hoje, usaremos o modelo Cloud Vision do Google, que podemos acessar usando a API do Google Cloud.
A próxima tecnologia que usaremos é a tradução automática. Isso, como o OCR, é um problema de aprendizado de máquina extremamente difícil. A linguagem humana é cheia de idiossincrasias e complexidades contextuais que tornam especialmente difícil para os computadores processá-la e entendê-la. A tradução entre pares de idiomas diferentes, como chinês e inglês, tende a produzir resultados particularmente imprecisos e engraçados devido à estrutura inerentemente diferente desses idiomas, exigindo estratégias muito diferentes para digitalização e incorporação. No entanto, apesar desses desafios, os pesquisadores desenvolveram modelos poderosos e sofisticados e os tornaram amplamente disponíveis. Hoje, usaremos a API de tradução do Google, uma das melhores e mais usadas ferramentas de tradução automática disponíveis.
A última tecnologia de aprendizado de máquina que usaremos é o LLM, ou Large Language Model, que foi revolucionário para a inteligência artificial do consumidor. O LLM é capaz de entender a estrutura da linguagem humana natural e é capaz de utilizar um grande corpo de dados para produzir uma resposta detalhada e informativa. Ainda existem muitas limitações para a tecnologia, mas sua flexibilidade e capacidade de processamento de dados inspiraram a criação de muitas novas técnicas para se envolver com o modelo. Uma dessas técnicas é chamada de Prompt Engineering , em que os usuários criam e ajustam entradas habilmente redigidas e estruturadas, ou prompts, para o modelo para obter o resultado desejado. No aplicativo de hoje, usaremos a API do ChatGPT e alguma engenharia de prompt simples para nos ajudar a analisar o relatório traduzido.
Antes de começarmos a codificar o aplicativo, precisamos primeiro nos inscrever nos serviços.
Como o site da API do ChatGPT está sempre mudando, não poderemos fornecer as etapas exatas para se inscrever na API do ChatGPT. No entanto, você deve encontrar instruções fáceis de seguir no site de documentação da API . Simplesmente progrida até obter uma chave de API, que precisaremos chamar de API ChatGPT.
O Google Cloud é um pouco mais complicado, mas também é relativamente simples de se inscrever. Basta acessar o console do Google Cloud e seguir as instruções para configurar um projeto. Uma vez no projeto, você vai querer navegar para o IAM & Admin console e criar uma conta de serviço. Embora o console do Google Cloud mude o tempo todo, você deve conseguir navegar na interface simplesmente procurando por "IAM" e "Conta de serviço" na página da web. Depois que a conta de serviço for criada, você desejará baixar a cópia do arquivo de chave privada para o seu computador. Você também desejará copiar a string da chave privada, pois a API REST de tradução usa a string da chave privada em vez do arquivo de chave.
Antes de encerrarmos a configuração do Google Cloud, você deve ativar a API Machine Vision, que pode ser feita na página principal do console. Basta pesquisar a API Machine Vision, clicar no produto do Google e ativar a API. Você também vai querer criar um depósito para armazenar os dados que usaremos para este projeto.
Agora que nos inscrevemos nos serviços adequados, estamos prontos para começar a codificar nosso aplicativo em Python. Em primeiro lugar, queremos instalar os pacotes necessários em nosso ambiente Python.
Pip install google-cloud-storage google-cloud-vision openai requests
Assim que a instalação estiver concluída, vamos criar uma nova pasta, baixar o arquivo PDF e criar um novo arquivo Python na mesma pasta. Vamos chamá-lo de document_analyze.py. Começamos importando os pacotes necessários:
import requests Import openai from google.cloud import vision from google.cloud import storage Import os Import json Import time Import shutil
Podemos então fazer algumas configurações básicas para que nosso aplicativo possa usar os serviços em nuvem nos quais acabamos de nos inscrever:
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = [Path to your Google Cloud key file] openai.api_key = [your openAI API key]
Com essas credenciais em vigor, agora você poderá acessar as APIs do Google Cloud e do ChatGPT a partir do seu script Python. Agora podemos escrever as funções que fornecerão a funcionalidade desejada para nosso aplicativo.
Agora podemos começar a construir algumas das funções que se tornarão os blocos de construção do aplicativo. Vamos começar com as funções 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')
Vamos examinar o que cada função faz em detalhes.
A função upload_file
é uma função que pega um bucket do contêiner do Google Cloud Storage e carrega seu arquivo nele. As excelentes abstrações do Google Cloud facilitam muito a criação dessa função.
A função async_detect_document
invoca de forma assíncrona a função OCR do Google Cloud. Devido ao número de opções disponíveis no Google Cloud, temos que instanciar alguns objetos de configuração, mas é apenas informar ao Google Cloud onde está o arquivo de origem e onde a saída deve ser gravada. A variável batch_size
é definida como 100, portanto, o Google Cloud processará o documento 100 páginas por vez. Isso reduz o número de arquivos de saída que são gravados, o que facilita o processamento. Outra coisa importante a observar é que a invocação é assíncrona, o que significa que a execução do script Python continuará em vez de aguardar a conclusão do processamento. Embora isso não faça muita diferença nesse estágio específico, ele se tornará mais útil posteriormente, quando transformarmos o código Python em uma API da web.
A função check_results
é uma função simples de armazenamento em nuvem para verificar se o processamento foi concluído. Como estamos invocando a função OCR de forma assíncrona, precisamos chamar essa função periodicamente para verificar se o arquivo de resultado está presente. Se houver um arquivo de resultado, a função retornará true e podemos continuar com a análise. Se não houver arquivo de resultado, a função retornará false e continuaremos esperando até que o processamento termine.
A função write_to_text
baixa o(s) arquivo(s) de resultado para o disco para processamento adicional. A função irá iterar sobre todos os arquivos em seu bucket com um determinado prefixo, recuperar a string de saída e gravar o resultado no sistema de arquivos local.
A função delete_objects
, embora não seja estritamente relevante para o OCR, limpa os arquivos enviados para que o sistema não mantenha artefatos desnecessários no Google Cloud Storage.
Agora que terminamos com as invocações de OCR, vamos ver o código de tradução automática!
Agora podemos definir as funções de tradução:
# 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']
Essas funções são bastante diretas. A função detect_language
chama a API de detecção de idioma para determinar o idioma de origem para a chamada translate_text
subsequente. Embora saibamos que o PDF está escrito em japonês, ainda é uma prática recomendada executar a detecção de idioma para que o aplicativo possa lidar com outros idiomas. A função translate_text
simplesmente usa a API de tradução do Google para traduzir o texto do idioma de origem detectado para o inglês, embora, se determinar que o idioma de origem já é o inglês, ela ignorará a tradução.
Por fim, temos as invocações ao 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']
Observe que a chamada do Python é uma chamada de API relativamente simples, mas o prompt é escrito de forma a produzir resultados específicos:
O prompt fornece o texto do relatório como contexto, para que o ChatGPT possa analisar o relatório facilmente. O texto é demarcado por traços, facilitando o reconhecimento de onde termina a reportagem e onde começam as perguntas.
As perguntas são enumeradas em vez de declaradas em um formato de parágrafo. A resposta, portanto, provavelmente seguirá uma estrutura semelhante. A estrutura enumerada torna o resultado muito mais fácil de analisar com o código do que se o ChatGPT respondesse no formato de parágrafo.
O prompt especifica o formato da resposta, neste caso, o formato JSON. O formato JSON é muito fácil de processar com código.
O prompt especifica as chaves do objeto JSON e escolhe as chaves que são muito fáceis de associar às perguntas.
As chaves também usam uma convenção comumente usada (camelCase) que o ChatGPT deve reconhecer. As chaves JSON são palavras completas em vez de contrações. Isso torna mais provável que o ChatGPT use a chave real na resposta, já que o ChatGPT tem o hábito de fazer “correções ortográficas” como parte de seu processamento.
O AddedContextString fornece uma saída para o ChatGPT passar informações adicionais. Isso aproveita a capacidade de análise de forma livre de grandes modelos de linguagem.
O prompt usa frases frequentemente encontradas em discussões técnicas sobre o assunto em questão. Como você deve ter percebido pela estrutura da chamada de API, o objetivo “verdadeiro” do ChatGPT não é necessariamente fornecer uma resposta ao prompt, mas sim prever a próxima linha em um diálogo.
Portanto, se seu prompt for formulado como uma linha de uma discussão de nível superficial, você provavelmente obterá uma resposta de nível superficial, enquanto se seu prompt for formulado como uma linha de uma discussão de especialista, é mais provável que você receba um resultado de especialista. Esse efeito é especialmente pronunciado para assuntos como matemática ou tecnologia, mas também é relevante aqui.
As técnicas acima são básicas em um novo corpo de trabalho chamado “engenharia de prompts”, onde o usuário estrutura seus prompts para obter um resultado específico. Com modelos de linguagem grandes como o ChatGPT, a criação cuidadosa do prompt pode resultar em aumentos drásticos na eficácia do modelo. Existem muitas semelhanças com a codificação, mas a engenharia imediata requer muito mais intuição e raciocínio confuso por parte do engenheiro. À medida que ferramentas como o ChatGPT se tornam mais incorporadas ao local de trabalho, a engenharia não será mais um empreendimento puramente técnico, mas também filosófico, e os engenheiros devem ter o cuidado de desenvolver sua intuição e uma “teoria da mente” para grandes modelos de linguagem, a fim de aumentar sua eficiência.
Agora vamos juntar tudo. Se você seguiu a seção anterior, o bloco de código abaixo deve ser bastante simples 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'])
Carregamos o relatório no bucket, iniciamos o OCR e aguardamos a conclusão do OCR. Em seguida, baixamos os resultados do OCR e os colocamos em uma lista. Traduzimos a lista de resultados, enviamos para o ChatGPT para análise e imprimimos o resultado da análise.
Como as ferramentas de IA não são determinísticas, se você executar esse código, provavelmente obterá um resultado semelhante, mas não idêntico. No entanto, aqui está o que obtive como saída, usando o PDF vinculado acima:
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 falante de japonês, posso verificar que a análise é muito boa! Você pode experimentar por conta própria, fornecendo seu próprio PDF e alterando as perguntas para atender às suas necessidades. Agora você tem uma poderosa ferramenta de IA para traduzir e resumir qualquer PDF em idioma estrangeiro que encontrar. Se você faz parte do público-alvo deste artigo, espero que esteja bastante empolgado com as possibilidades que acabaram de se abrir!
O código completo do back-end pode ser encontrado no meu GitHub
Já construímos um aplicativo poderoso, mas em sua forma atual, o usuário terá que conhecer um pouco de Python para usar o aplicativo em toda a sua extensão. E se você tivesse um usuário que não deseja ler ou escrever nenhum código? Nesse caso, queremos construir um aplicativo da web em torno dessa ferramenta, para que as pessoas possam acessar todo o poder da IA no conforto de um navegador.
Vamos começar criando um aplicativo React. Certifique-se de que o Node esteja instalado, navegue até a pasta onde deseja que o código do aplicativo fique, execute o script create-react-app e instale alguns pacotes básicos:
npx create-react-app llm-frontend
E então:
cd llm-frontend npm install bootstrap react-bootstrap axios
Se estivéssemos desenvolvendo um aplicativo completo, também gostaríamos de instalar pacotes para lidar com gerenciamento de estado e roteamento, mas isso está fora do escopo deste artigo. Simplesmente faremos edições no arquivo App.jsx.
Execute npm run start
para iniciar o servidor de desenvolvimento e seu navegador deve abrir uma página para http://localhost:3000 . Mantenha essa página e abra o arquivo App.jsx em seu editor de texto favorito. Você deve ver algo assim:
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;
Vá em frente e exclua o código clichê e substitua-o por alguns componentes básicos do 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;
Salve o aplicativo e você deverá vê-lo atualizado no navegador. Vai parecer bem escasso agora, mas não se preocupe, vamos corrigir isso em breve.
Para que este aplicativo funcione, precisaremos de quatro componentes principais: um seletor de arquivos para carregar o arquivo, um display de resultados para exibir a tradução e o resumo, uma entrada de texto para que o usuário possa fazer suas próprias perguntas e um display de resultados para exibir a resposta às perguntas do usuário.
Podemos construir os componentes mais simples e colocar espaços reservados para as interfaces mais complexas por enquanto. Já que estamos nisso, vamos criar os contêineres de dados que usaremos para alimentar a 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>); }
Há um pouco de código novo, mas não há nada complexo ou inovador sobre o novo código. O código é apenas uma entrada de dados básica e interface de exibição baseada em componentes React-Bootstrap.
Salve o arquivo e você deverá ver a atualização do seu navegador para mostrar a interface de upload do arquivo.
Brinque com as variáveis e você verá uma interface como esta. Esta será a interface front-end do nosso aplicativo.
Agora que escrevemos a interface básica do front-end, vamos escrever as funções que conectarão o aplicativo à nossa API (ainda não escrita). Essas funções serão todas definidas no objeto app para que ele tenha acesso a todos os hooks do React. Se você não tiver certeza de onde essas funções devem ir, consulte o código completo hospedado no GitHub .
Primeiro, vamos escrever algumas funções utilitárias para passar mensagens ao usuário.
const flashMessageBuilder = (setMessage) => (message) => { setMessage(message); setTimeout(() => { setMessage(''); }, (5000)); } const flashMessage = flashMessageBuilder(setMessage); const flashUserQuestionMessage = flashMessageBuilder(setUserQuestionMessage);
Como você pode ver, são funções simples que exibem uma mensagem no local apropriado e criam um tempo para remover a mensagem após 5 segundos. Este é um recurso de interface do usuário simples, mas torna o aplicativo muito mais dinâmico e utilizável.
Em seguida, vamos escrever as funções para analisar o arquivo e verificar os 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.'); }) }
Novamente, um conjunto bastante simples de funções. A função AnalyzeFile envia o arquivo para o endpoint analyze_file para análise. A API fornecerá um ID de lote que ele usa para verificar os resultados com a função pollForResults. A função pollForResults atingirá o endpoint check_if_finished e retornará os resultados se a análise for concluída e aguardará 5 segundos se a análise ainda estiver sendo processada. O “thread” do analyzeFile continuará a ser executado, colocando os dados nos locais apropriados.
Por fim, vamos escrever a função que permite ao usuário fazer perguntas de forma livre:
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'); }); }
Novamente, uma função bastante simples. Fornecemos o texto traduzido junto com a pergunta do usuário para que nossa API possa construir o prompt do ChatGPT. O resultado é então enviado para os contêineres de dados apropriados para exibição.
Acabamos de usar o aplicativo React, mas antes de passarmos para a codificação da API, vamos fazer mais uma alteração cosmética. No momento, a exibição do resultado da análise está configurada para exibir a análise do ChatGPT como uma string. No entanto, a análise do ChatGPT é, na verdade, um objeto de dados JSON, portanto, para exibi-lo adequadamente para uso humano, desejaremos adicionar alguma formatação ao objeto de exibição. Substitua o primeiro item Acordeão pelo seguinte 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>
Agora que o front-end está pronto, vamos ao nosso código Python e construímos o back-end.
Primeiro, vamos instalar o Flask, que usaremos para escrever nosso back-end.
Pip install flask flask-cors
Flask é uma estrutura simples para criar aplicativos da web e APIs da web. A interface é incrivelmente simples e colocar um servidor em execução é tão fácil quanto:
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)
Execute este arquivo e navegue até http://localhost:5000 em seu navegador, e você verá a mensagem “Hello from flask!” mensagem.
Agora podemos começar a construir a funcionalidade da API. Vamos começar importando as funções necessárias e definindo algumas 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 assume que o código do seu servidor está na mesma pasta que o arquivo document_analyze.py que escrevemos anteriormente, mas você pode escolher qualquer estrutura de diretório que desejar, desde que o código do servidor possa encontrar e importar de document_analyze.py. Vamos escrever o manipulador para o endpoint de upload de arquivo:
@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 você pode ver, esta função pega o arquivo carregado, envia-o para o Google Cloud Storage e inicia o processo de OCR. Deve parecer bastante familiar, mas aqui estão algumas pequenas mudanças que valem a pena apontar. Primeiro, o arquivo é identificado por um UUID que também serve como nome do lote. Isso evita possíveis problemas de colisão que podem surgir da API sendo chamada várias vezes e também identifica exclusivamente todos os arquivos usados em um determinado lote de análise, facilitando a verificação do progresso e a limpeza posterior.
Vamos agora escrever o manipulador que permite que o aplicativo verifique se a análise foi concluída.
@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) })
Novamente, isso deve parecer bastante familiar. Primeiro verificamos se o OCR foi feito e, se não, simplesmente retornamos uma mensagem informando que o lote ainda está sendo processado. Se o OCR for concluído, continuamos a análise, baixando os resultados do OCR e executando a tradução e o pipeline do ChatGPT. Também nos certificamos de limpar os arquivos de origem após a conclusão da análise, a fim de evitar custos de armazenamento desnecessários. Empacotamos o resultado no objeto de resultado final, que contém o JSON de análise do ChatGPT, o texto traduzido e o texto bruto extraído pelo OCR.
Embora o back-end de perguntas personalizadas seja um novo recurso, ele é bastante direto. Primeiro, queremos definir a função para fazer uma pergunta 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']
Agora podemos importar a função e definir o endpoint da 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 })
Agora que escrevemos nossa API, vamos testá-la no frontend. Vá em frente e carregue o arquivo pelo frontend. Você deve ver algo assim:
Aguarde um pouco e você verá o resultado no WebApp.
Olhe em volta e você verá o texto traduzido também:
E o texto fonte bruto:
Agora vamos testar a interface de perguntas personalizadas:
Muito bom! Com isso, construímos com sucesso um aplicativo React.js em torno de nossas ferramentas de IA!
No artigo de hoje, criamos um aplicativo que utiliza algumas das ferramentas de IA mais poderosas atualmente no mercado. Embora esse aplicativo específico seja voltado para analisar e resumir PDFs em idiomas estrangeiros, técnicas semelhantes podem ser adaptadas para desenvolver aplicativos revolucionários poderosos em muitos campos.
Espero que este artigo tenha inspirado você a escrever seus próprios aplicativos baseados em IA. Se você quiser entrar em contato comigo e falar sobre sua perspectiva sobre aplicativos de IA, adoraria ouvir sua opinião. Se você deseja criar um aplicativo semelhante, sinta-se à vontade para consultar o código hospedado na minha página do Github , onde você pode encontrar repositórios para o Front-end e o Back-end
Se você quiser usar este aplicativo sem precisar construí-lo por conta própria, criei e hospedei uma versão mais sofisticada do aplicativo para uso geral. Se você deseja acessar este aplicativo, entre em contato e podemos fornecer acesso ao site.
Fique atento a um artigo de acompanhamento, onde construímos e executamos nossa própria instância de um LLM. Quando o artigo for publicado, publicaremos um link para o artigo aqui. Fique atento!