Update backend/main.py
Browse files- backend/main.py +43 -22
backend/main.py
CHANGED
|
@@ -35,8 +35,7 @@ app.add_middleware(
|
|
| 35 |
# --- 2. Configurações de IA ---
|
| 36 |
HF_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
# e forçar o uso do modelo novo e funcional.
|
| 40 |
model = ChatGroq(
|
| 41 |
model="llama-3.3-70b-versatile",
|
| 42 |
temperature=0.3
|
|
@@ -47,7 +46,6 @@ embeddings = HuggingFaceEmbeddings(
|
|
| 47 |
model_kwargs={'device': 'cpu'}
|
| 48 |
)
|
| 49 |
|
| 50 |
-
# System Prompt Otimizado
|
| 51 |
rag_system_prompt = (
|
| 52 |
"Você é um assistente experiente e prestativo. "
|
| 53 |
"Sua tarefa é fornecer respostas detalhadas e ricas em contexto com base nas informações fornecidas. "
|
|
@@ -60,7 +58,9 @@ rag_prompt = ChatPromptTemplate.from_messages(
|
|
| 60 |
[("system", rag_system_prompt), ("human", "{input}")]
|
| 61 |
)
|
| 62 |
|
|
|
|
| 63 |
rag_chain = None
|
|
|
|
| 64 |
|
| 65 |
# --- 3. Modelo de Dados ---
|
| 66 |
class ChatRequest(BaseModel):
|
|
@@ -69,7 +69,7 @@ class ChatRequest(BaseModel):
|
|
| 69 |
# --- 4. Helpers ---
|
| 70 |
def format_docs(docs):
|
| 71 |
return "\n\n---\n\n".join(
|
| 72 |
-
f"Conteúdo: {doc.page_content}\n(Página {doc.metadata.get('page', 'N/A')})"
|
| 73 |
for doc in docs
|
| 74 |
)
|
| 75 |
|
|
@@ -77,7 +77,7 @@ def format_docs(docs):
|
|
| 77 |
|
| 78 |
@app.post("/upload-document")
|
| 79 |
async def upload_document(file: UploadFile = File(...)):
|
| 80 |
-
global rag_chain
|
| 81 |
|
| 82 |
if file.content_type != "application/pdf":
|
| 83 |
raise HTTPException(status_code=400, detail="Apenas arquivos PDF são suportados.")
|
|
@@ -91,24 +91,16 @@ async def upload_document(file: UploadFile = File(...)):
|
|
| 91 |
loader = PyPDFLoader(temp_path)
|
| 92 |
docs = loader.load()
|
| 93 |
|
| 94 |
-
# Chunking otimizado
|
| 95 |
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=200)
|
| 96 |
splits = text_splitter.split_documents(docs)
|
| 97 |
|
| 98 |
vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
|
| 99 |
|
| 100 |
-
#
|
| 101 |
-
|
| 102 |
|
| 103 |
-
#
|
| 104 |
-
rag_chain = (
|
| 105 |
-
RunnablePassthrough.assign(
|
| 106 |
-
context=(lambda x: x["input"]) | retriever | format_docs
|
| 107 |
-
)
|
| 108 |
-
| rag_prompt
|
| 109 |
-
| model
|
| 110 |
-
| StrOutputParser()
|
| 111 |
-
)
|
| 112 |
|
| 113 |
return {
|
| 114 |
"message": "Processamento concluído!",
|
|
@@ -126,19 +118,48 @@ async def upload_document(file: UploadFile = File(...)):
|
|
| 126 |
|
| 127 |
@app.post("/chat")
|
| 128 |
async def chat(request: ChatRequest):
|
|
|
|
|
|
|
|
|
|
| 129 |
current_chain = rag_chain
|
|
|
|
|
|
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
)
|
| 135 |
-
current_chain = base_prompt | model | StrOutputParser()
|
| 136 |
|
|
|
|
| 137 |
async def stream_generator():
|
| 138 |
try:
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
if chunk:
|
| 141 |
yield chunk
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
except Exception as e:
|
| 143 |
print(f"Erro stream: {e}")
|
| 144 |
yield f"Erro no serviço de IA: {e}"
|
|
|
|
| 35 |
# --- 2. Configurações de IA ---
|
| 36 |
HF_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
|
| 37 |
|
| 38 |
+
# Configuração do Modelo (Ignorando variáveis antigas para garantir funcionamento)
|
|
|
|
| 39 |
model = ChatGroq(
|
| 40 |
model="llama-3.3-70b-versatile",
|
| 41 |
temperature=0.3
|
|
|
|
| 46 |
model_kwargs={'device': 'cpu'}
|
| 47 |
)
|
| 48 |
|
|
|
|
| 49 |
rag_system_prompt = (
|
| 50 |
"Você é um assistente experiente e prestativo. "
|
| 51 |
"Sua tarefa é fornecer respostas detalhadas e ricas em contexto com base nas informações fornecidas. "
|
|
|
|
| 58 |
[("system", rag_system_prompt), ("human", "{input}")]
|
| 59 |
)
|
| 60 |
|
| 61 |
+
# Variáveis globais para armazenar a "inteligência" do RAG
|
| 62 |
rag_chain = None
|
| 63 |
+
global_retriever = None # MUDANÇA: Retriever global para acessarmos no chat
|
| 64 |
|
| 65 |
# --- 3. Modelo de Dados ---
|
| 66 |
class ChatRequest(BaseModel):
|
|
|
|
| 69 |
# --- 4. Helpers ---
|
| 70 |
def format_docs(docs):
|
| 71 |
return "\n\n---\n\n".join(
|
| 72 |
+
f"📄 Conteúdo: {doc.page_content}\n(🔖 Fonte: Página {doc.metadata.get('page', 'N/A')})"
|
| 73 |
for doc in docs
|
| 74 |
)
|
| 75 |
|
|
|
|
| 77 |
|
| 78 |
@app.post("/upload-document")
|
| 79 |
async def upload_document(file: UploadFile = File(...)):
|
| 80 |
+
global rag_chain, global_retriever
|
| 81 |
|
| 82 |
if file.content_type != "application/pdf":
|
| 83 |
raise HTTPException(status_code=400, detail="Apenas arquivos PDF são suportados.")
|
|
|
|
| 91 |
loader = PyPDFLoader(temp_path)
|
| 92 |
docs = loader.load()
|
| 93 |
|
|
|
|
| 94 |
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=200)
|
| 95 |
splits = text_splitter.split_documents(docs)
|
| 96 |
|
| 97 |
vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
|
| 98 |
|
| 99 |
+
# MUDANÇA: Salvamos o retriever globalmente
|
| 100 |
+
global_retriever = vectorstore.as_retriever(search_kwargs={"k": 6})
|
| 101 |
|
| 102 |
+
# A chain agora é simples, pois faremos a recuperação manual no endpoint chat
|
| 103 |
+
rag_chain = rag_prompt | model | StrOutputParser()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
return {
|
| 106 |
"message": "Processamento concluído!",
|
|
|
|
| 118 |
|
| 119 |
@app.post("/chat")
|
| 120 |
async def chat(request: ChatRequest):
|
| 121 |
+
"""
|
| 122 |
+
Endpoint de chat com Auditoria (envia contexto ao final)
|
| 123 |
+
"""
|
| 124 |
current_chain = rag_chain
|
| 125 |
+
context_str = ""
|
| 126 |
+
docs_source = []
|
| 127 |
|
| 128 |
+
# 1. Recuperação Manual de Contexto (Se o RAG estiver ativo)
|
| 129 |
+
if global_retriever:
|
| 130 |
+
try:
|
| 131 |
+
# Busca os documentos relevantes
|
| 132 |
+
docs_source = global_retriever.invoke(request.content)
|
| 133 |
+
context_str = format_docs(docs_source)
|
| 134 |
+
except Exception as e:
|
| 135 |
+
print(f"Erro na recuperação: {e}")
|
| 136 |
+
context_str = "Erro ao recuperar contexto."
|
| 137 |
+
else:
|
| 138 |
+
# Fallback se não houver PDF
|
| 139 |
+
current_chain = (
|
| 140 |
+
ChatPromptTemplate.from_messages([("system", "Você é um assistente útil."), ("human", "{input}")])
|
| 141 |
+
| model
|
| 142 |
+
| StrOutputParser()
|
| 143 |
)
|
|
|
|
| 144 |
|
| 145 |
+
# 2. Gerador de Streaming com "Payload Oculto"
|
| 146 |
async def stream_generator():
|
| 147 |
try:
|
| 148 |
+
# Passa o contexto manualmente para o prompt
|
| 149 |
+
input_data = {"input": request.content}
|
| 150 |
+
if context_str:
|
| 151 |
+
input_data["context"] = context_str
|
| 152 |
+
|
| 153 |
+
# Stream da resposta da IA
|
| 154 |
+
async for chunk in current_chain.astream(input_data):
|
| 155 |
if chunk:
|
| 156 |
yield chunk
|
| 157 |
+
|
| 158 |
+
# MUDANÇA: Ao final, enviamos um separador e o contexto para auditoria
|
| 159 |
+
if context_str:
|
| 160 |
+
debug_data = f"\n\n###__DEBUG__###\n**Auditoria de Contexto (RAG):**\n\n{context_str}"
|
| 161 |
+
yield debug_data
|
| 162 |
+
|
| 163 |
except Exception as e:
|
| 164 |
print(f"Erro stream: {e}")
|
| 165 |
yield f"Erro no serviço de IA: {e}"
|