Como comentado no meu post anterior, eu desenvolvi uma arquitetura de serviços para tentar economizar um pouco na hora de consumir a API da OpenAI e o modelo gpt-3.5-turbo.
Agora vou mostrar um pouco do código que inseri no Firebase functions que é chamado toda vez uma nova perguntar é inserida no Firestore.
const functions = require("firebase-functions"); | |
require("dotenv").config(); | |
exports.answerQuestion = functions.firestore | |
.document("/questions/{questionId}") | |
.onCreate((snap, context) => { | |
const data = snap.data(); | |
functions.logger.log( | |
"Answering question", | |
context.params.questionId, | |
data.title, | |
); | |
fetch(process.env.RENDER_API_URL, { | |
method: "post", | |
body: JSON.stringify({ | |
data, | |
collection: "questions", | |
document: context.params.questionId, | |
}), | |
headers: { | |
"Content-Type": "application/json", | |
"Authorization": `Bearer ${process.env.SECURITY_TOKEN}`, | |
}, | |
}); | |
}); |
Essa função irá enviar os dados da pergunta cadastrada pelo usuário, para um serviço no Render.com onde a API do modelo do ChatGPT será consumida.
Vale relembrar o motivo de eu não ter feito tudo do lado do Firebase Cloud Function:
O Cloud Function cobra de acordo com o tempo de execução da sua função, além do número de invocações e dos recursos provisionados. Como a API do ChatGPT pode demorar para responder, dependendo da complexidade da sua consulta, você pode acabar pagando muito pelo tempo que a sua função fica aguardando a resposta da API.
Ao fim do processo, a pergunta terá sua resposta atualizada no Firestore com base nos dados recibidos da API do ChatGPT.
import os | |
import time | |
import threading | |
import firebase_admin | |
from dotenv import load_dotenv | |
from firebase_admin import firestore, credentials | |
from langchain.schema import AIMessage | |
from flask import Flask, request | |
from flask_httpauth import HTTPTokenAuth | |
from ask import execute as ask_execute | |
from query import upsert_question, execute as query_execute | |
load_dotenv() | |
cred = credentials.Certificate("./serviceAccountKey.json") | |
firebase_admin.initialize_app(cred) | |
app = Flask(__name__) | |
auth = HTTPTokenAuth(scheme='Bearer') | |
tokens = { | |
os.environ.get('FIREBASE_TOKEN'): "firebase" | |
} | |
@auth.verify_token | |
def verify_token(token): | |
if token in tokens: | |
return tokens[token] | |
@app.route('/answer_question', methods=['POST']) | |
@auth.login_required | |
def answer_question(): | |
data = request.get_json() | |
def long_running_task(**kwargs): | |
params = kwargs.get( | |
'post_data', {"data": {}, "collection": "", "document": ""} | |
) | |
data = params["data"] | |
collection_path = params["collection"] | |
document_path = params["document"] | |
client = firestore.client() | |
affected_doc = client.collection( | |
collection_path).document(document_path) | |
answer = None | |
question = data["title"] | |
similarity_search = query_execute(question, k=1, namespace="questions") | |
if similarity_search: | |
document, score = similarity_search[0] | |
if score > 0.95: | |
answer = AIMessage(content=document.metadata['answer']) | |
if not answer: | |
answer = ask_execute(question) | |
upsert_question(question, answer.content) | |
affected_doc.update({ | |
u'answer': answer.content | |
}) | |
thread = threading.Thread( | |
target=long_running_task, | |
kwargs={'post_data': data} | |
) | |
thread.start() | |
return {"message": "Accepted"}, 202 | |
if __name__ == "__main__": | |
app.run(host="0.0.0.0", debug=True) |
Podemos destacar alguns trechos importantes do código anterior:
Linhas 13 e 14: São métodos customizados que fazem a comunicação com o Pinecone e a API da OpenAI. Sugiro buscar mais informações em https://python.langchain.com/en/latest/use_cases/question_answering.html
Linha 60: Nas linhas anteriores, o código é responsável por buscar na base de dados de perguntas já realizadas pelos usuários e encontrar a pergunta mais parecida. Com base na pergunta mais parecida já feita anteriormente, a linha 60 é responsável por verificar se a similaridade é tão próxima (95%) que a resposta da pergunta anterior pode ser utilizada para responder a nova pergunta. Como comentei no meu post anterior, esse comparativo não se daria muito bem para diferenciar perguntas como: Quanto custa 1kg do seu produto?’ e ‘Quanto custa 1g do seu produto?’.
Linha 71: Essa parte do código foi responsável por sanar meu problema com o delay da API da OpenAI. Alguns podem estar se perguntando o porquê de eu não ter utilizado algo relacionado a filas de processamento em background. Mas como comentei no post anterior, meu objetivo por agora é buscar alternativas mais baratas. Contratar um banco Redis e um worker em periodo integral, não está nos meu planos por agora. Mas, mudar isso, com certeza está em um dos meu planos futuros.
Top comments (0)