chatbotlaguna / main.py
Rocha
Added Route
3e73034
Raw
History Blame Contribute Delete
7.46 kB
from fastapi import FastAPI, HTTPException, UploadFile, File
from pydantic import BaseModel
import os
import uuid
import shutil
import certifi
from langchain_community.vectorstores import Chroma
from langchain.storage import InMemoryStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.messages import HumanMessage
from langchain_community.llms import HuggingFaceHub
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from unstructured.partition.pdf import partition_pdf
from base64 import b64decode
from datetime import datetime
from werkzeug.utils import secure_filename
from fastapi.middleware.cors import CORSMiddleware
from pymongo import MongoClient
from bson.objectid import ObjectId
from dotenv import load_dotenv
load_dotenv()
app = FastAPI()
# Ruta para la ra铆z
# Configuraci贸n de CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Permite todos los or铆genes (cambiar en producci贸n)
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Configuraci贸n de las claves de API
os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY")
os.environ["LANGCHAIN_API_KEY"] = os.getenv("LANGCHAIN_API_KEY")
os.environ["LANGCHAIN_TRACING_V2"] = os.getenv("LANGCHAIN_TRACING_V2")
os.environ["HUGGINGFACEHUB_API_TOKEN"] = os.getenv("HUGGINGFACEHUB_API_TOKEN")
os.environ['SSL_CERT_FILE'] = certifi.where()
# Configuraci贸n de la carpeta temporal
TEMP_FOLDER = os.getenv('TEMP_FOLDER', './_temp')
os.makedirs(TEMP_FOLDER, exist_ok=True)
# Configuraci贸n de MongoDB
MONGO_URI = os.getenv("MONGO_URI")
client = MongoClient(MONGO_URI, tls=True, tlsCAFile=certifi.where())
db = client.chatbotlaguna
conversations_collection = db.conversations
documents_collection = db.documents
# Configuraci贸n del vectorstore
vectorstore = Chroma(
collection_name="multi_modal_rag",
embedding_function=HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
)
store = InMemoryStore()
id_key = "doc_id"
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key=id_key,
)
# Configuraci贸n del modelo de Hugging Face
llm = HuggingFaceHub(
repo_id="mistralai/Mistral-7B-Instruct-v0.2",
model_kwargs={"temperature": 0.5, "max_length": 512}
)
# Funci贸n para parsear documentos
def parse_docs(docs):
b64 = []
text = []
for doc in docs:
try:
b64decode(doc)
b64.append(doc)
except Exception:
text.append(doc)
return {"images": b64, "texts": text}
# Funci贸n para construir el prompt
def build_prompt(kwargs):
docs_by_type = kwargs["context"]
user_question = kwargs["question"]
context_text = "".join(text_element.text for text_element in docs_by_type["texts"])
prompt_template = f"""
Answer the question based only on the following context, which can include text, tables, and images.
Context: {context_text}
Question: {user_question}
"""
prompt_content = [{"type": "text", "text": prompt_template}]
if len(docs_by_type["images"]) > 0:
for image in docs_by_type["images"]:
prompt_content.append(
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image}"},
}
)
return ChatPromptTemplate.from_messages(
[
HumanMessage(content=prompt_content),
]
)
# Cadena de procesamiento
chain = (
{
"context": retriever | RunnableLambda(parse_docs),
"question": RunnablePassthrough(),
}
| RunnableLambda(build_prompt)
| llm
| StrOutputParser()
)
chain_with_sources = {
"context": retriever | RunnableLambda(parse_docs),
"question": RunnablePassthrough(),
} | RunnablePassthrough().assign(
response=(
RunnableLambda(build_prompt)
| llm
| StrOutputParser()
)
)
# Modelo de solicitud
class QueryRequest(BaseModel):
question: str
conversation_name: str = "Default Conversation"
# Ruta para incrustar archivos PDF
@app.post("/embed")
async def embed_pdf(file: UploadFile = File(...)):
if not file.filename.endswith(".pdf"):
raise HTTPException(status_code=400, detail="Solo se permiten archivos PDF")
# Guardar el archivo en la carpeta de documentos
file_path = f"./documents/{secure_filename(file.filename)}"
with open(file_path, "wb") as f:
shutil.copyfileobj(file.file, f)
# Procesar el PDF
chunks = partition_pdf(
filename=file_path,
infer_table_structure=True,
strategy="hi_res",
extract_image_block_types=["Image"],
extract_image_block_to_payload=True,
chunking_strategy="by_title",
max_characters=10000,
combine_text_under_n_chars=2000,
new_after_n_chars=6000,
)
# Separar textos, tablas e im谩genes
texts = [chunk for chunk in chunks if "CompositeElement" in str(type(chunk))]
tables = [chunk for chunk in chunks if "Table" in str(type(chunk))]
images = get_images_base64(chunks)
# Agregar textos al vectorstore
doc_ids = [str(uuid.uuid4()) for _ in texts]
summary_texts = [
Document(page_content=chunk.text, metadata={id_key: doc_ids[i]}) for i, chunk in enumerate(texts)
]
retriever.vectorstore.add_documents(summary_texts)
retriever.docstore.mset(list(zip(doc_ids, texts)))
# Guardar la informaci贸n del documento en MongoDB
document_data = {
"file_path": file_path,
"doc_ids": doc_ids,
"texts": [chunk.text for chunk in texts],
"tables": [chunk.text for chunk in tables],
"images": images,
"upload_date": datetime.now()
}
documents_collection.insert_one(document_data)
return {"message": "PDF procesado y datos incrustados correctamente"}
# Ruta para realizar consultas
@app.post("/query")
async def query(request: QueryRequest):
try:
response = chain.invoke(request.question)
# Guardar la conversaci贸n en MongoDB
conversation_data = {
"conversation_name": request.conversation_name,
"question": request.question,
"response": response,
"timestamp": datetime.now()
}
conversations_collection.insert_one(conversation_data)
return {"response": response}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Funci贸n para obtener im谩genes en base64
def get_images_base64(chunks):
images_b64 = []
for chunk in chunks:
if "CompositeElement" in str(type(chunk)):
chunk_els = chunk.metadata.orig_elements
for el in chunk_els:
if "Image" in str(type(el)):
images_b64.append(el.metadata.image_base64)
return images_b64
# Crear la carpeta de documentos si no existe
if not os.path.exists("./documents"):
os.makedirs("./documents")
# Iniciar la aplicaci贸n
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)