caarleexx commited on
Commit
b618a56
·
verified ·
1 Parent(s): 102b0ec

Update backend/main.py

Browse files
Files changed (1) hide show
  1. backend/main.py +27 -25
backend/main.py CHANGED
@@ -12,7 +12,8 @@ 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
17
  from langchain_text_splitters import RecursiveCharacterTextSplitter
18
  from langchain_community.vectorstores import FAISS
@@ -35,7 +36,7 @@ app.add_middleware(
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
@@ -50,6 +51,7 @@ 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. "
52
  "Ao usar o contexto abaixo, sintetize os pontos principais e explique como eles se relacionam. "
 
53
  "Se a resposta não estiver no contexto, diga honestamente que não sabe, não invente informações."
54
  "\n\nCONTEXTO DO DOCUMENTO:\n{context}"
55
  )
@@ -58,9 +60,8 @@ rag_prompt = ChatPromptTemplate.from_messages(
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,7 +70,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(🔖 Fonte: Página {doc.metadata.get('page', 'N/A')})"
73
  for doc in docs
74
  )
75
 
@@ -79,34 +80,46 @@ def format_docs(docs):
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.")
 
 
 
 
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
  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!",
107
  "filename": file.filename,
108
- "total_pages": len(docs),
109
- "total_chunks": len(splits)
110
  }
111
 
112
  except Exception as e:
@@ -118,44 +131,33 @@ async def upload_document(file: UploadFile = File(...)):
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
 
12
  from fastapi.responses import StreamingResponse
13
 
14
  # RAG Imports
15
+ # MUDANÇA: Adicionado TextLoader para arquivos de texto
16
+ from langchain_community.document_loaders import PyPDFLoader, TextLoader
17
  from langchain_community.embeddings import HuggingFaceEmbeddings
18
  from langchain_text_splitters import RecursiveCharacterTextSplitter
19
  from langchain_community.vectorstores import FAISS
 
36
  # --- 2. Configurações de IA ---
37
  HF_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
38
 
39
+ # Modelo Groq
40
  model = ChatGroq(
41
  model="llama-3.3-70b-versatile",
42
  temperature=0.3
 
51
  "Você é um assistente experiente e prestativo. "
52
  "Sua tarefa é fornecer respostas detalhadas e ricas em contexto com base nas informações fornecidas. "
53
  "Ao usar o contexto abaixo, sintetize os pontos principais e explique como eles se relacionam. "
54
+ "Se o contexto for código (Python/MD), explique o funcionamento ou use como referência."
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
  )
 
60
  [("system", rag_system_prompt), ("human", "{input}")]
61
  )
62
 
 
63
  rag_chain = None
64
+ global_retriever = None
65
 
66
  # --- 3. Modelo de Dados ---
67
  class ChatRequest(BaseModel):
 
70
  # --- 4. Helpers ---
71
  def format_docs(docs):
72
  return "\n\n---\n\n".join(
73
+ f"📄 Conteúdo: {doc.page_content}\n(🔖 Fonte: {doc.metadata.get('source', 'Desconhecida')})"
74
  for doc in docs
75
  )
76
 
 
80
  async def upload_document(file: UploadFile = File(...)):
81
  global rag_chain, global_retriever
82
 
83
+ # 1. Validação de extensão
84
+ filename = file.filename.lower()
85
+ allowed_extensions = [".pdf", ".txt", ".md", ".py"]
86
+
87
+ if not any(filename.endswith(ext) for ext in allowed_extensions):
88
+ raise HTTPException(status_code=400, detail=f"Extensão não suportada. Use: {allowed_extensions}")
89
 
90
  try:
91
+ # Salva arquivo temporário com a extensão correta (importante para o Loader)
92
+ file_ext = os.path.splitext(filename)[1]
93
+ with tempfile.NamedTemporaryFile(delete=False, suffix=file_ext) as tmp_file:
94
  content = await file.read()
95
  tmp_file.write(content)
96
  temp_path = tmp_file.name
97
 
98
+ # 2. Seleção do Loader baseado na extensão
99
+ if filename.endswith(".pdf"):
100
+ loader = PyPDFLoader(temp_path)
101
+ else:
102
+ # Para .txt, .md, .py usamos o TextLoader com UTF-8
103
+ loader = TextLoader(temp_path, encoding="utf-8")
104
+
105
  docs = loader.load()
106
 
107
+ # 3. Chunking
108
+ # Se for código (.py), talvez chunks menores sejam melhores, mas manteremos o padrão por enquanto
109
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=200)
110
  splits = text_splitter.split_documents(docs)
111
 
112
+ # 4. Vetorização
113
  vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
 
 
114
  global_retriever = vectorstore.as_retriever(search_kwargs={"k": 6})
115
 
 
116
  rag_chain = rag_prompt | model | StrOutputParser()
117
 
118
  return {
119
  "message": "Processamento concluído!",
120
  "filename": file.filename,
121
+ "total_chunks": len(splits),
122
+ "type": file_ext
123
  }
124
 
125
  except Exception as e:
 
131
 
132
  @app.post("/chat")
133
  async def chat(request: ChatRequest):
 
 
 
134
  current_chain = rag_chain
135
  context_str = ""
 
136
 
 
137
  if global_retriever:
138
  try:
 
139
  docs_source = global_retriever.invoke(request.content)
140
  context_str = format_docs(docs_source)
141
  except Exception as e:
142
  print(f"Erro na recuperação: {e}")
143
  context_str = "Erro ao recuperar contexto."
144
  else:
 
145
  current_chain = (
146
  ChatPromptTemplate.from_messages([("system", "Você é um assistente útil."), ("human", "{input}")])
147
  | model
148
  | StrOutputParser()
149
  )
150
 
 
151
  async def stream_generator():
152
  try:
 
153
  input_data = {"input": request.content}
154
  if context_str:
155
  input_data["context"] = context_str
156
 
 
157
  async for chunk in current_chain.astream(input_data):
158
  if chunk:
159
  yield chunk
160
 
 
161
  if context_str:
162
  debug_data = f"\n\n###__DEBUG__###\n**Auditoria de Contexto (RAG):**\n\n{context_str}"
163
  yield debug_data