caarleexx commited on
Commit
de649f0
·
verified ·
1 Parent(s): 6b6b272

Update backend/main.py

Browse files
Files changed (1) hide show
  1. backend/main.py +132 -27
backend/main.py CHANGED
@@ -1,62 +1,167 @@
 
1
 
2
  import os
 
 
3
  from dotenv import load_dotenv
4
- from fastapi import FastAPI
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from pydantic import BaseModel
7
  from langchain_core.prompts import ChatPromptTemplate
8
  from langchain_groq import ChatGroq
9
  from fastapi.responses import StreamingResponse
10
 
11
- # Carrega a API key do arquivo .env
 
 
 
 
 
 
 
 
12
  load_dotenv()
13
 
14
- # 1. Inicialização do FastAPI
15
  app = FastAPI()
16
 
17
- # 2. Configuração do CORS
18
- # Essencial para permitir que o frontend (rodando em outra porta) se comunique com este backend.
19
  app.add_middleware(
20
  CORSMiddleware,
21
- allow_origins=["*"], # Permite todas as origens (em produção, restrinja para o seu domínio)
22
  allow_credentials=True,
23
  allow_methods=["*"],
24
  allow_headers=["*"],
25
  )
26
 
27
- # 3. LangChain: Definição do Modelo e do Prompt
28
- # Usamos o LangChain para simplificar a interação com a Groq
29
- model = ChatGroq(model="groq/compound")
 
 
 
 
 
 
 
 
 
30
 
31
- system_prompt = (
 
32
  "Você é um assistente de pesquisa avançado. "
33
- "Use as ferramentas disponíveis para responder às perguntas do usuário de forma completa e detalhada."
 
 
 
34
  )
35
- prompt = ChatPromptTemplate.from_messages(
36
- [("system", system_prompt), ("human", "{input}")]
37
  )
38
 
39
- # Criamos a "cadeia" de execução: o prompt é passado para o modelo
40
- chain = prompt | model
 
41
 
42
- # 4. Pydantic Model: Define a estrutura da requisição que nossa API espera
43
  class ChatRequest(BaseModel):
44
  content: str
45
 
46
- # 5. O Endpoint da API
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  @app.post("/chat")
48
  async def chat(request: ChatRequest):
49
  """
50
- Este endpoint recebe uma mensagem do usuário e retorna um stream da resposta do modelo.
51
  """
 
 
 
 
 
 
 
 
 
 
52
  # Função geradora que produz os pedaços (chunks) da resposta
53
  async def stream_generator():
54
- # 'astream' é o método de streaming assíncrono do LangChain
55
- async for chunk in chain.astream({"input": request.content}):
56
- if chunk.content:
57
- # 'yield' envia o pedaço de texto para o cliente
58
- print(f"Enviando chunk: {chunk.content}") # Log para depuração
59
- yield chunk.content
60
-
61
- # Retorna uma resposta de streaming que consome o nosso gerador
62
- return StreamingResponse(stream_generator(), media_type="text/plain")
 
 
 
 
 
 
 
1
+ #--- START OF FILE main.py ---
2
 
3
  import os
4
+ import io
5
+ import tempfile
6
  from dotenv import load_dotenv
7
+ from fastapi import FastAPI, UploadFile, File, HTTPException
8
  from fastapi.middleware.cors import CORSMiddleware
9
  from pydantic import BaseModel
10
  from langchain_core.prompts import ChatPromptTemplate
11
  from langchain_groq import ChatGroq
12
  from fastapi.responses import StreamingResponse
13
 
14
+ # RAG Imports
15
+ from langchain_community.document_loaders import PyPDFLoader
16
+ from langchain_community.embeddings import HuggingFaceEmbeddings # MUDANÇA: Novo import
17
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
18
+ from langchain_community.vectorstores import FAISS
19
+ from langchain_core.runnables import RunnablePassthrough, RunnableLambda
20
+ from langchain_core.output_parsers import StrOutputParser
21
+
22
+ # Carrega as API keys do arquivo .env
23
  load_dotenv()
24
 
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=["*"],
32
  allow_credentials=True,
33
  allow_methods=["*"],
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 e o modelo de embedding
42
+ model = ChatGroq(model=os.getenv("GROQ_MODEL", "mixtral-8x7b-32768"))
43
+ # MUDANÇA: Inicializa o HuggingFaceEmbeddings
44
+ embeddings = HuggingFaceEmbeddings(
45
+ model_name=HF_EMBEDDING_MODEL,
46
+ # O device="cpu" garante que ele será executado na CPU, o que é ideal em ambientes sem GPU.
47
+ model_kwargs={'device': 'cpu'}
48
+ )
49
 
50
+ # Prompt RAG modificado para receber contexto
51
+ rag_system_prompt = (
52
  "Você é um assistente de pesquisa avançado. "
53
+ "Use o CONTEXTO fornecido para responder à pergunta do usuário. "
54
+ "Responda de forma completa e detalhada, citando o contexto sempre que possível. "
55
+ "Se o contexto não for suficiente, diga que não tem informações suficientes."
56
+ "\n\nCONTEXTO: {context}"
57
  )
58
+ rag_prompt = ChatPromptTemplate.from_messages(
59
+ [("system", rag_system_prompt), ("human", "{input}")]
60
  )
61
 
62
+ # A chain será inicializada com o endpoint de upload
63
+ rag_chain = None
64
+
65
 
66
+ # --- 3. Pydantic Model ---
67
  class ChatRequest(BaseModel):
68
  content: str
69
 
70
+
71
+ # --- 4. Funções de RAG (Helper Functions) ---
72
+
73
+ def format_docs(docs):
74
+ """Formata os documentos recuperados em uma string única para injeção no prompt."""
75
+ # Adicionar metadados (se existirem) pode ajudar o LLM a "citar" o documento
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
+ # Mantenha essa lógica pois o PyPDFLoader precisa de um caminho de arquivo
96
+ try:
97
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
98
+ # Garante que o arquivo temporário está no disco para o PyPDFLoader
99
+ content = await file.read()
100
+ tmp_file.write(content)
101
+ temp_path = tmp_file.name
102
+
103
+ # 2. Carregar o documento
104
+ loader = PyPDFLoader(temp_path)
105
+ docs = loader.load()
106
+
107
+ # 3. Particionar (Chunking)
108
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
109
+ splits = text_splitter.split_documents(docs)
110
+
111
+ # 4. Vetorizar e Armazenar (Vector Store)
112
+ # Atenção: O download do modelo HF (se for a primeira vez) pode demorar!
113
+ vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
114
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # Recupera os 4 melhores chunks
115
+
116
+ # 5. Criar a nova Chain RAG
117
+ rag_chain = (
118
+ RunnablePassthrough.assign(context=retriever | format_docs)
119
+ | rag_prompt
120
+ | model
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}. Verifique se o modelo HuggingFace foi baixado corretamente.")
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
+ # Aqui, mantemos o output parser para consistência com a chain RAG
149
+ current_chain = base_prompt | model | StrOutputParser()
150
+
151
  # Função geradora que produz os pedaços (chunks) da resposta
152
  async def stream_generator():
153
+ try:
154
+ # 'astream' é o método de streaming assíncrono do LangChain
155
+ async for chunk in current_chain.astream({"input": request.content}):
156
+ if chunk:
157
+ # print(f"Enviando chunk: {chunk}") # Log para depuração
158
+ yield chunk
159
+ except Exception as e:
160
+ # Caso a chamada Groq falhe ou outro erro ocorra
161
+ print(f"Erro no streaming: {e}")
162
+ # Emite o erro para o cliente
163
+ yield f"Erro no serviço de IA: {e}"
164
+
165
+ # Retorna uma resposta de streaming
166
+ return StreamingResponse(stream_generator(), media_type="text/plain")
167
+ #--- END OF FILE main.py ---