caarleexx commited on
Commit
3196290
·
verified ·
1 Parent(s): 5d7e5a4

Update backend/main.py

Browse files
Files changed (1) hide show
  1. backend/main.py +34 -49
backend/main.py CHANGED
@@ -25,7 +25,6 @@ load_dotenv()
25
  # --- 1. Inicialização e Configuração ---
26
  app = FastAPI()
27
 
28
- # Configuração do CORS
29
  app.add_middleware(
30
  CORSMiddleware,
31
  allow_origins=["*"],
@@ -34,84 +33,74 @@ app.add_middleware(
34
  allow_headers=["*"],
35
  )
36
 
37
- # --- 2. Variáveis Globais para RAG ---
38
- # Define o modelo de embedding do Hugging Face (leve para CPU)
39
  HF_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
40
 
41
- # Inicializa o modelo Groq
42
- # CORREÇÃO AQUI: Atualizado para 'llama-3.3-70b-versatile' pois o mixtral foi descontinuado
43
- model = ChatGroq(model=os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile"))
 
 
44
 
45
- # Inicializa o HuggingFaceEmbeddings na CPU
46
  embeddings = HuggingFaceEmbeddings(
47
  model_name=HF_EMBEDDING_MODEL,
48
  model_kwargs={'device': 'cpu'}
49
  )
50
 
51
- # Prompt RAG modificado para receber contexto
52
  rag_system_prompt = (
53
- "Você é um assistente de pesquisa avançado. "
54
- "Use o CONTEXTO fornecido para responder à pergunta do usuário. "
55
- "Responda de forma completa e detalhada, citando o contexto sempre que possível. "
56
- "Se o contexto não for suficiente, diga que não tem informações suficientes."
57
- "\n\nCONTEXTO: {context}"
58
  )
 
59
  rag_prompt = ChatPromptTemplate.from_messages(
60
  [("system", rag_system_prompt), ("human", "{input}")]
61
  )
62
 
63
- # A chain será inicializada com o endpoint de upload
64
  rag_chain = None
65
 
66
-
67
  # --- 3. Pydantic Model ---
68
  class ChatRequest(BaseModel):
69
  content: str
70
 
71
-
72
- # --- 4. Funções de RAG (Helper Functions) ---
73
-
74
  def format_docs(docs):
75
- """Formata os documentos recuperados em uma string única para injeção no prompt."""
76
  return "\n\n---\n\n".join(
77
- f"Conteúdo: {doc.page_content}\n(Fonte: Página {doc.metadata.get('page', 'N/A')})"
78
  for doc in docs
79
  )
80
 
81
-
82
- # --- 5. Endpoints da API ---
83
 
84
  @app.post("/upload-document")
85
  async def upload_document(file: UploadFile = File(...)):
86
- """
87
- Este endpoint recebe um arquivo, processa-o e inicializa o vetorstore e o retriever.
88
- """
89
  global rag_chain
90
 
91
  if file.content_type != "application/pdf":
92
- raise HTTPException(status_code=400, detail="Apenas arquivos PDF são suportados neste exemplo.")
93
 
94
- # 1. Salvar o arquivo temporariamente para o Loader poder ler
95
  try:
96
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
97
  content = await file.read()
98
  tmp_file.write(content)
99
  temp_path = tmp_file.name
100
 
101
- # 2. Carregar o documento
102
  loader = PyPDFLoader(temp_path)
103
  docs = loader.load()
104
 
105
- # 3. Particionar (Chunking)
106
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
107
  splits = text_splitter.split_documents(docs)
108
 
109
- # 4. Vetorizar e Armazenar (Vector Store)
110
  vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
111
- retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
112
 
113
- # 5. Criar a nova Chain RAG
114
- # Correção mantida: lambda para extrair 'input' do dicionário
 
 
115
  rag_chain = (
116
  RunnablePassthrough.assign(
117
  context=(lambda x: x["input"]) | retriever | format_docs
@@ -121,44 +110,40 @@ async def upload_document(file: UploadFile = File(...)):
121
  | StrOutputParser()
122
  )
123
 
124
- return {"message": f"Documento '{file.filename}' processado e RAG pronto!"}
 
 
 
 
 
 
125
 
126
  except Exception as e:
127
- print(f"Erro no processamento do arquivo: {e}")
128
- # Retorna um erro 500 para o frontend
129
- raise HTTPException(status_code=500, detail=f"Falha ao processar o arquivo: {e}")
130
  finally:
131
- # Limpeza: deletar o arquivo temporário
132
  if 'temp_path' in locals() and os.path.exists(temp_path):
133
  os.remove(temp_path)
134
 
135
-
136
  @app.post("/chat")
137
  async def chat(request: ChatRequest):
138
- """
139
- Endpoint de chat que usa o RAG (se inicializado) ou o modelo base.
140
- """
141
  current_chain = rag_chain
142
 
143
  if current_chain is None:
144
- # Fallback para a chain original (apenas prompt/sem contexto)
145
  base_prompt = ChatPromptTemplate.from_messages(
146
- [("system", "Você é um assistente prestativo. Nenhuma informação de documento foi fornecida."), ("human", "{input}")]
147
  )
148
  current_chain = base_prompt | model | StrOutputParser()
149
 
150
- # Função geradora que produz os pedaços (chunks) da resposta
151
  async def stream_generator():
152
  try:
153
- # 'astream' é o método de streaming assíncrono do LangChain
154
  async for chunk in current_chain.astream({"input": request.content}):
155
  if chunk:
156
  yield chunk
157
  except Exception as e:
158
- print(f"Erro no streaming: {e}")
159
  yield f"Erro no serviço de IA: {e}"
160
 
161
- # Retorna uma resposta de streaming
162
  return StreamingResponse(stream_generator(), media_type="text/plain")
163
 
164
  #--- END OF FILE main (1).py ---
 
25
  # --- 1. Inicialização e Configuração ---
26
  app = FastAPI()
27
 
 
28
  app.add_middleware(
29
  CORSMiddleware,
30
  allow_origins=["*"],
 
33
  allow_headers=["*"],
34
  )
35
 
36
+ # --- 2. Configurações de IA Otimizadas (Baseado no app 26) ---
 
37
  HF_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
38
 
39
+ # MUDANÇA 1: Temperatura controlada (0.3 é melhor para fatos/RAG)
40
+ model = ChatGroq(
41
+ model=os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile"),
42
+ temperature=0.3
43
+ )
44
 
 
45
  embeddings = HuggingFaceEmbeddings(
46
  model_name=HF_EMBEDDING_MODEL,
47
  model_kwargs={'device': 'cpu'}
48
  )
49
 
50
+ # MUDANÇA 2: System Prompt Melhorado (Inspirado no app 26)
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. "
54
+ "Ao usar o contexto abaixo, sintetize os pontos principais e explique como eles se relacionam. "
55
+ "Se a resposta não estiver no contexto, diga honestamente que não sabe, não invente informações."
56
+ "\n\nCONTEXTO DO DOCUMENTO:\n{context}"
57
  )
58
+
59
  rag_prompt = ChatPromptTemplate.from_messages(
60
  [("system", rag_system_prompt), ("human", "{input}")]
61
  )
62
 
 
63
  rag_chain = None
64
 
 
65
  # --- 3. Pydantic Model ---
66
  class ChatRequest(BaseModel):
67
  content: str
68
 
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
 
76
+ # --- 5. Endpoints ---
 
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.")
84
 
 
85
  try:
86
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
87
  content = await file.read()
88
  tmp_file.write(content)
89
  temp_path = tmp_file.name
90
 
 
91
  loader = PyPDFLoader(temp_path)
92
  docs = loader.load()
93
 
94
+ # MUDANÇA 3: Chunking um pouco maior para pegar mais contexto
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
+ # MUDANÇA 4: k=6 (Recupera mais pedaços para o Llama 3.3 analisar)
101
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 6})
102
+
103
+ # Chain com a correção do lambda (dict input)
104
  rag_chain = (
105
  RunnablePassthrough.assign(
106
  context=(lambda x: x["input"]) | retriever | format_docs
 
110
  | StrOutputParser()
111
  )
112
 
113
+ # MUDANÇA 5: Retorno com estatísticas (igual ao app 26)
114
+ return {
115
+ "message": "Processamento concluído!",
116
+ "filename": file.filename,
117
+ "total_pages": len(docs),
118
+ "total_chunks": len(splits)
119
+ }
120
 
121
  except Exception as e:
122
+ print(f"Erro: {e}")
123
+ raise HTTPException(status_code=500, detail=f"Falha ao processar: {e}")
 
124
  finally:
 
125
  if 'temp_path' in locals() and os.path.exists(temp_path):
126
  os.remove(temp_path)
127
 
 
128
  @app.post("/chat")
129
  async def chat(request: ChatRequest):
 
 
 
130
  current_chain = rag_chain
131
 
132
  if current_chain is None:
 
133
  base_prompt = ChatPromptTemplate.from_messages(
134
+ [("system", "Você é um assistente útil. Nenhum documento foi carregado ainda."), ("human", "{input}")]
135
  )
136
  current_chain = base_prompt | model | StrOutputParser()
137
 
 
138
  async def stream_generator():
139
  try:
 
140
  async for chunk in current_chain.astream({"input": request.content}):
141
  if chunk:
142
  yield chunk
143
  except Exception as e:
144
+ print(f"Erro stream: {e}")
145
  yield f"Erro no serviço de IA: {e}"
146
 
 
147
  return StreamingResponse(stream_generator(), media_type="text/plain")
148
 
149
  #--- END OF FILE main (1).py ---