Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -141,134 +141,90 @@ def sanitizar_texto(texto: str) -> str:
|
|
| 141 |
# CORREÇÃO DA FUNÇÃO DE CHAMADA DA API (google-genai v1.0+)
|
| 142 |
# ===========================================================================
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
try:
|
| 149 |
-
#
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
]
|
| 156 |
|
| 157 |
-
#
|
| 158 |
tools = [
|
| 159 |
types.Tool(google_search=types.GoogleSearch()),
|
| 160 |
]
|
| 161 |
|
| 162 |
-
#
|
| 163 |
-
#
|
| 164 |
-
#
|
| 165 |
-
thinking_config = None
|
| 166 |
-
if "thinking" in model_name: # Lógica opcional para ativar apenas se for modelo thinking
|
| 167 |
-
thinking_config = types.ThinkingConfig(thinking_budget=8192)
|
| 168 |
-
|
| 169 |
generate_content_config = types.GenerateContentConfig(
|
| 170 |
-
temperature=
|
| 171 |
max_output_tokens=max_tokens,
|
| 172 |
-
thinking_config=thinking_config,
|
| 173 |
-
tools=tools
|
| 174 |
-
response_mime_type="application/json" # Força JSON nativo se o modelo suportar
|
| 175 |
)
|
| 176 |
|
| 177 |
-
#
|
| 178 |
-
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
stream = CLIENT.models.generate_content_stream(
|
| 181 |
model=model_name,
|
| 182 |
contents=contents,
|
| 183 |
config=generate_content_config,
|
| 184 |
)
|
| 185 |
|
| 186 |
-
#
|
| 187 |
resposta_bruta = ""
|
| 188 |
for chunk in stream:
|
|
|
|
| 189 |
if chunk.text:
|
| 190 |
resposta_bruta += chunk.text
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
except:
|
| 207 |
-
return {"erro": "JSON_NOT_FOUND", "detalhes": "Nenhum objeto JSON encontrado na resposta da API.", "raw": resposta_sanitizada}
|
| 208 |
-
|
| 209 |
-
return json.loads(match.group(0))
|
| 210 |
|
| 211 |
except Exception as e:
|
| 212 |
-
logger.log(f"Falha na chamada da API
|
| 213 |
-
return {"erro": "
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
def chamar_gemini_json1(model_name: str, prompt: str, temperatura: float = 0.4, max_tokens: int = 92048) -> Dict:
|
| 218 |
-
prompt_completo = f"{prompt}\n\n---\n\n**INSTRUÇÃO OBRIGATÓRIA: Sua resposta DEVE ser um único e válido objeto JSON.**"
|
| 219 |
-
print(f"\n{'='*25} 💬 API INPUT PARA [{model_name}] {'='*25}\n{prompt_completo}\n{'='*78}\n")
|
| 220 |
|
| 221 |
-
try:
|
| 222 |
-
# 1. Monta a estrutura de `contents`
|
| 223 |
-
contents = [
|
| 224 |
-
types.Content(
|
| 225 |
-
role="user",
|
| 226 |
-
parts=[types.Part.from_text(text=prompt_completo)],
|
| 227 |
-
),
|
| 228 |
-
]
|
| 229 |
-
|
| 230 |
-
# 2. Define as ferramentas a serem usadas
|
| 231 |
-
tools = [
|
| 232 |
-
types.Tool(google_search=types.GoogleSearch()),
|
| 233 |
-
]
|
| 234 |
-
|
| 235 |
-
# 3. Monta a configuração da geração de conteúdo
|
| 236 |
-
# A sintaxe do 'thinking_config' foi corrigida.
|
| 237 |
-
generate_content_config = types.GenerateContentConfig(
|
| 238 |
-
temperature=temperatura,
|
| 239 |
-
max_output_tokens=max_tokens,
|
| 240 |
-
thinking_config=types.ThinkingConfig(thinking_budget=8192), # Habilitar se o modelo suportar
|
| 241 |
-
tools=tools,
|
| 242 |
-
)
|
| 243 |
-
|
| 244 |
-
# 4. Chama o método de stream e agrega os chunks
|
| 245 |
-
stream = CLIENT.generate_content(
|
| 246 |
-
model=f"{model_name}", # O nome do modelo agora precisa do prefixo 'models/'
|
| 247 |
-
contents=contents,
|
| 248 |
-
generation_config=generate_content_config,
|
| 249 |
-
stream=True
|
| 250 |
-
)
|
| 251 |
|
| 252 |
-
# Agrega a resposta do stream em uma única string
|
| 253 |
-
resposta_bruta = "".join(chunk.text for chunk in stream)
|
| 254 |
-
|
| 255 |
-
print(f"\n{'='*25} 📥 API RAW OUTPUT DE [{model_name}] {'='*25}\n{resposta_bruta}\n{'='*78}\n")
|
| 256 |
-
|
| 257 |
-
resposta_sanitizada = sanitizar_texto(resposta_bruta)
|
| 258 |
|
| 259 |
-
if not resposta_sanitizada:
|
| 260 |
-
logger.log("A API retornou uma resposta vazia. Causa provável: Filtros de segurança.", "WARN")
|
| 261 |
-
return {"erro": "API_EMPTY_RESPONSE", "causa_provavel": "Filtro de segurança do modelo."}
|
| 262 |
-
|
| 263 |
-
match = re.search(r'\{.*\}', resposta_sanitizada, re.DOTALL)
|
| 264 |
-
if not match:
|
| 265 |
-
return {"erro": "JSON_NOT_FOUND", "detalhes": "Nenhum objeto JSON encontrado na resposta da API."}
|
| 266 |
-
|
| 267 |
-
return json.loads(match.group(0))
|
| 268 |
-
|
| 269 |
-
except Exception as e:
|
| 270 |
-
logger.log(f"Falha na chamada da API ou no parse do JSON: {e}", "ERROR")
|
| 271 |
-
return {"erro": "API_CALL_OR_PARSE_FAILED", "detalhes": str(e)}
|
| 272 |
|
| 273 |
def criar_dna() -> Dict:
|
| 274 |
return { "historico_chat": [], "meta": {"total_turnos": 0}, "pipeline_state": { "status": "completed", "paused_at_step": None, "saved_data": {} } }
|
|
|
|
| 141 |
# CORREÇÃO DA FUNÇÃO DE CHAMADA DA API (google-genai v1.0+)
|
| 142 |
# ===========================================================================
|
| 143 |
|
| 144 |
+
# ============================================================================
|
| 145 |
+
# FUNÇÃO CORRIGIDA: REMOVIDO response_mime_type PARA COMPATIBILIDADE COM TOOLS
|
| 146 |
+
# ============================================================================
|
| 147 |
+
|
| 148 |
+
def chamar_gemini_json(model_name: str, prompt: str, temperatura: float = 0.7, max_tokens: int = 12000) -> Dict:
|
| 149 |
+
# 1. Ajuste de Prompt
|
| 150 |
+
# Modelos "Thinking" funcionam melhor quando explicamos o que queremos,
|
| 151 |
+
# mas mantemos a proibição de Markdown no output final para facilitar o regex.
|
| 152 |
+
prompt_completo = f"{prompt}\n\n---\n\n**INSTRUÇÃO CRÍTICA: Ao final do seu raciocínio, sua resposta FINAL deve ser estritamente um único objeto JSON válido. Não use blocos de código markdown"
|
| 153 |
+
|
| 154 |
+
print(f"\n{'='*25} 💬 API INPUT PARA [{model_name}] {'='*25}\n{prompt_completo[:300]}...\n{'='*78}\n")
|
| 155 |
|
| 156 |
try:
|
| 157 |
+
# 2. Configurar o Thinking
|
| 158 |
+
# include_thoughts=True fará com que o pensamento apareça no output (ajuda a debugar),
|
| 159 |
+
# mas o seu regex filtrará para pegar só o JSON depois.
|
| 160 |
+
thinking_config = types.ThinkingConfig(
|
| 161 |
+
include_thoughts=True
|
| 162 |
+
)
|
|
|
|
| 163 |
|
| 164 |
+
# 3. Ferramentas (Google Search)
|
| 165 |
tools = [
|
| 166 |
types.Tool(google_search=types.GoogleSearch()),
|
| 167 |
]
|
| 168 |
|
| 169 |
+
# 4. Configuração da Geração
|
| 170 |
+
# ATENÇÃO: Modelos "Thinking" exigem temperatura maior ou igual a 0.7 para serem criativos no raciocínio.
|
| 171 |
+
# removemos 'response_mime_type' pois conflita com Tools e Thinking juntos.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
generate_content_config = types.GenerateContentConfig(
|
| 173 |
+
temperature=0.7, # Thinking requer > 0
|
| 174 |
max_output_tokens=max_tokens,
|
| 175 |
+
thinking_config=thinking_config,
|
| 176 |
+
tools=tools
|
|
|
|
| 177 |
)
|
| 178 |
|
| 179 |
+
# 5. Configurar Conteúdo
|
| 180 |
+
contents = [
|
| 181 |
+
types.Content(
|
| 182 |
+
role="user",
|
| 183 |
+
parts=[types.Part.from_text(text=prompt_completo)],
|
| 184 |
+
),
|
| 185 |
+
]
|
| 186 |
+
|
| 187 |
+
# 6. Chamada API
|
| 188 |
stream = CLIENT.models.generate_content_stream(
|
| 189 |
model=model_name,
|
| 190 |
contents=contents,
|
| 191 |
config=generate_content_config,
|
| 192 |
)
|
| 193 |
|
| 194 |
+
# 7. Agregar Resposta
|
| 195 |
resposta_bruta = ""
|
| 196 |
for chunk in stream:
|
| 197 |
+
# Captura partes de texto e partes de pensamento (se retornadas como texto no stream)
|
| 198 |
if chunk.text:
|
| 199 |
resposta_bruta += chunk.text
|
| 200 |
+
# Modelos Thinking as vezes retornam "executable_code", garantimos pegar apenas texto
|
| 201 |
+
|
| 202 |
+
print(f"\n{'='*25} 📥 API RAW OUTPUT (Thinking + JSON) {'='*25}\n{resposta_bruta[:500]}... [conteudo longo] ...\n{'='*78}\n")
|
| 203 |
|
| 204 |
+
# 8. Sanitização e Extração
|
| 205 |
+
# Como o modelo vai "pensar" primeiro, o texto vai começar com o raciocínio e terminar com o JSON.
|
| 206 |
+
# O Regex aqui é CRUCIAL.
|
| 207 |
+
match = re.search(r'(\{.*\})', resposta_bruta, re.DOTALL)
|
| 208 |
|
| 209 |
+
if match:
|
| 210 |
+
json_str = match.group(0)
|
| 211 |
+
return json.loads(json_str)
|
| 212 |
+
else:
|
| 213 |
+
# Fallback: Tenta limpar chars estranhos caso o regex falhe
|
| 214 |
+
logger.log("Regex primário falhou, tentando limpeza forçada...", "WARN")
|
| 215 |
+
resposta_sanitizada = sanitizar_texto(resposta_bruta)
|
| 216 |
+
match_retry = re.search(r'(\{.*\})', resposta_sanitizada, re.DOTALL)
|
| 217 |
+
if match_retry:
|
| 218 |
+
return json.loads(match_retry.group(0))
|
| 219 |
+
|
| 220 |
+
return {"erro": "JSON_NOT_FOUND", "raw": resposta_bruta[:500], "detalhes": "O modelo pensou mas não entregou o JSON no formato esperado."}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
except Exception as e:
|
| 223 |
+
logger.log(f"Falha na chamada da API com Thinking: {e}", "ERROR")
|
| 224 |
+
return {"erro": "API_CALL_FAILED", "detalhes": str(e)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
def criar_dna() -> Dict:
|
| 230 |
return { "historico_chat": [], "meta": {"total_turnos": 0}, "pipeline_state": { "status": "completed", "paused_at_step": None, "saved_data": {} } }
|