csc-cia-stne 0.1.31__py3-none-any.whl → 0.1.33__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
csc_cia_stne/__init__.py CHANGED
@@ -17,6 +17,7 @@ from .wacess import Waccess
17
17
  from .gcp_bucket import GCPBucket
18
18
  from .ftp import FTP
19
19
  from .gcp_document_ai import GCPDocumentAIClient
20
+ from .jerry import JerryClient
20
21
 
21
22
  # Define os itens disponíveis para importação
22
23
  __all__ = [
@@ -36,6 +37,7 @@ __all__ = [
36
37
  "web_screen",
37
38
  "Waccess",
38
39
  "GCPBucket",
40
+ "JerryClient"
39
41
  "FTP"
40
42
  ]
41
43
 
csc_cia_stne/jerry.py ADDED
@@ -0,0 +1,592 @@
1
+ import requests
2
+ import json
3
+ import re
4
+ import openai
5
+ import time
6
+ import logging
7
+ import base64
8
+ from pydantic import BaseModel, ValidationError, field_validator
9
+ from typing import List, Dict, Any, Optional
10
+ from datetime import datetime
11
+
12
+ jerry_prompt = "Jerry: "
13
+
14
+ class JerryConfigValidator(BaseModel):
15
+ """Validador para configurações do Jerry usando Pydantic V2"""
16
+ username: str
17
+ password: str
18
+ verbose: bool = False
19
+ api_base_url: str
20
+ ia_model: str
21
+
22
+ @field_validator('username', 'password', 'api_base_url', 'ia_model')
23
+ @classmethod
24
+ def validate_non_empty_string(cls, v: str) -> str:
25
+ """Valida se o campo é uma string não vazia"""
26
+ if not isinstance(v, str):
27
+ raise ValueError('deve ser uma string')
28
+ if not v.strip():
29
+ raise ValueError('não pode estar vazio')
30
+ return v.strip()
31
+
32
+ @field_validator('api_base_url')
33
+ @classmethod
34
+ def validate_url_format(cls, v: str) -> str:
35
+ """Valida se a URL tem formato básico"""
36
+ v = v.strip()
37
+ if not (v.startswith('http://') or v.startswith('https://')):
38
+ raise ValueError('deve começar com http:// ou https://')
39
+ return v
40
+
41
+ class InterpretarTextoValidator(BaseModel):
42
+ """Validador para o método interpretar_texto"""
43
+ system_prompt: str
44
+ user_prompt: str
45
+
46
+ @field_validator('system_prompt', 'user_prompt')
47
+ @classmethod
48
+ def validate_prompts(cls, v: str) -> str:
49
+ """Valida se os prompts são strings válidas para interpretação"""
50
+ if not isinstance(v, str):
51
+ raise ValueError('prompt deve ser uma string')
52
+
53
+ # Remove espaços em branco e caracteres de controle
54
+ texto_limpo = v.strip()
55
+
56
+ if not texto_limpo:
57
+ raise ValueError('prompt não pode estar vazio')
58
+ if len(texto_limpo) < 3:
59
+ raise ValueError('prompt deve ter pelo menos 3 caracteres')
60
+ if len(v) > 1000000:
61
+ raise ValueError('prompt excede o limite máximo de 1.000.000 caracteres')
62
+
63
+ # Validação adicional para caracteres especiais excessivos
64
+ if len([c for c in texto_limpo if c.isalpha()]) < 2:
65
+ raise ValueError('prompt deve conter pelo menos 2 caracteres alfabéticos')
66
+
67
+ return texto_limpo
68
+
69
+ class JerryClient:
70
+ def __init__(self, username: str, password: str, api_base_url: str = "https://cia-api-jerry.karavela-shared-stg.aws.karavela.run", ia_model: str = "databricks-llama-4-maverick", verbose: bool = False):
71
+ """
72
+ Inicializa o cliente Jerry com validação usando Pydantic V2.
73
+
74
+ Args:
75
+ username (str): Nome de usuário para autenticação
76
+ password (str): Senha para autenticação
77
+ api_url (str): URL da API do Jerry
78
+ ia_model (str): Modelo de IA a ser usado
79
+ verbose (bool): Se o modulo vai fazer output de logs (default = False)
80
+
81
+ Raises:
82
+ ValueError: Se algum parâmetro for inválido
83
+ """
84
+
85
+ try:
86
+ # Validação usando Pydantic V2
87
+ config = JerryConfigValidator(
88
+ username=username,
89
+ password=password,
90
+ verbose=verbose,
91
+ api_base_url=api_base_url,
92
+ ia_model=ia_model
93
+ )
94
+
95
+ # Atribuição dos valores validados
96
+ self.verbose = verbose
97
+ self.username = config.username
98
+ self.password = config.password
99
+ self.verbose = config.verbose
100
+ self.log = logging.getLogger("__main__")
101
+ if self.verbose:
102
+ self.log.setLevel(logging.INFO)
103
+ else:
104
+ self.log.setLevel(logging.CRITICAL)
105
+
106
+ self.api_base_url = config.api_base_url.rstrip('/')
107
+ self.api_login_url = f"{self.api_base_url}/login"
108
+ self.api_v1_url = f"{self.api_base_url}/v1/databricks"
109
+ self.ia_model = config.ia_model
110
+ self.is_connected = False
111
+ self.error = None
112
+ self.client = None
113
+ self.headers_authenticated = None
114
+ self.token = self._autenticar()
115
+
116
+ except ValidationError as e:
117
+ self.is_connected = False
118
+ error_details = []
119
+ for error in e.errors():
120
+ field = error['loc'][0] if error['loc'] else 'unknown'
121
+ message = error['msg']
122
+ error_details.append(f"{field}: {message}")
123
+
124
+ error_msg = f"Erro na validação dos parâmetros do Jerry: {'; '.join(error_details)}"
125
+ self.log.error(error_msg)
126
+ self.is_connected = False
127
+ self.error = error_msg
128
+
129
+ except Exception as e:
130
+ error_msg = f"Erro inesperado na inicialização do Jerry: {e}"
131
+ self.log.error(error_msg)
132
+ self.is_connected = False
133
+ self.error = error_msg
134
+
135
+ def _autenticar(self) -> Optional[dict]:
136
+ try:
137
+ headers_not_authenticated = {
138
+ "Content-Type": "application/json"
139
+ }
140
+ response = requests.post(self.api_login_url, json={"email": self.username, "password": self.password}, headers=headers_not_authenticated, timeout=60)
141
+ token = response.json().get("access_token", None)
142
+ if not token:
143
+ raise Exception("Token de autenticação não recebido")
144
+ self.headers_authenticated = {
145
+ "Content-Type": "application/json",
146
+ "Authorization": f"Bearer {token}"
147
+ }
148
+ self.client = openai.OpenAI(
149
+ base_url=self.api_v1_url,
150
+ api_key=token,
151
+ timeout=60
152
+ )
153
+ self.is_connected = True
154
+ self.error = None
155
+ return token
156
+ except Exception as e:
157
+ self.is_connected = False
158
+ self.error = f"Erro na autenticação: {e}"
159
+ return None
160
+
161
+ def v1_enviar_para_ia(self, system_prompt: str, user_prompt: str, arquivos: Optional[List[Dict[str, str]]] = None) -> Dict[str, Any]:
162
+ """
163
+ Envia prompts e opcionalmente múltiplas imagens para a IA via Databricks.
164
+
165
+ IMPORTANTE: Este método aceita APENAS imagens. Outros tipos de arquivo serão rejeitados.
166
+
167
+ Args:
168
+ system_prompt (str): Prompt do sistema para configurar o comportamento da IA
169
+ user_prompt (str): Prompt do usuário com a solicitação específica
170
+ arquivos (Optional[List[Dict[str, str]]]): Lista de imagens, cada uma com:
171
+ - base64: String base64 da imagem (sem prefixo data:)
172
+ - mime_type: Tipo MIME da imagem (deve ser image/*)
173
+ - name: Nome/descrição da imagem (opcional, para logs)
174
+ - file_path: Caminho original da imagem (opcional, para logs)
175
+
176
+ Tipos de imagem suportados:
177
+ ✅ image/jpeg, image/jpg, image/png, image/gif, image/webp, image/bmp, image/tiff
178
+ ❌ Qualquer outro tipo será rejeitado com erro específico
179
+
180
+ Returns:
181
+ Dict[str, Any]: Resultado da operação contendo:
182
+ - success (bool): True se operação foi bem-sucedida
183
+ - error (str|None): Mensagem de erro específica se houver falha
184
+ - content (dict|None): Conteúdo processado da resposta da IA
185
+ - input_tokens (int): Número de tokens de entrada utilizados
186
+ - output_tokens (int): Número de tokens de saída gerados
187
+ - total_tokens (int): Total de tokens utilizados
188
+ - images_validated (dict): Estatísticas das imagens validadas
189
+
190
+ Validações realizadas:
191
+ 1. Prompts não podem estar vazios
192
+ 2. Arquivos devem ter mime_type de imagem
193
+ 3. Base64 deve ser válido e decodificável
194
+ 4. Imagem decodificada deve ter conteúdo
195
+ 5. Tamanho total das imagens não deve exceder 50MB
196
+ """
197
+
198
+ def _validar_base64_formato(base64_string: str) -> bool:
199
+ """Valida o formato da string base64"""
200
+ try:
201
+ # Remove espaços e quebras de linha
202
+ base64_clean = base64_string.strip().replace('\n', '').replace('\r', '').replace(' ', '')
203
+
204
+ if not base64_clean:
205
+ return False
206
+
207
+ # Verifica se tem apenas caracteres válidos de base64
208
+ if not re.match(r'^[A-Za-z0-9+/]*={0,2}$', base64_clean):
209
+ return False
210
+
211
+ # Verifica se o comprimento é múltiplo de 4
212
+ if len(base64_clean) % 4 != 0:
213
+ return False
214
+
215
+ return True
216
+
217
+ except Exception:
218
+ return False
219
+
220
+ def _validar_cabecalho_imagem(data: bytes, mime_type: str) -> bool:
221
+ """Valida se os bytes iniciais correspondem ao tipo de imagem"""
222
+ try:
223
+ if len(data) < 10:
224
+ return False
225
+
226
+ # Assinaturas de arquivo por tipo
227
+ signatures = {
228
+ 'image/jpeg': [b'\xff\xd8\xff'],
229
+ 'image/jpg': [b'\xff\xd8\xff'],
230
+ 'image/png': [b'\x89PNG\r\n\x1a\n'],
231
+ 'image/gif': [b'GIF87a', b'GIF89a'],
232
+ 'image/webp': [b'RIFF', b'WEBP'],
233
+ 'image/bmp': [b'BM'],
234
+ 'image/tiff': [b'II*\x00', b'MM\x00*']
235
+ }
236
+
237
+ expected_sigs = signatures.get(mime_type.lower(), [])
238
+
239
+ for sig in expected_sigs:
240
+ if data.startswith(sig):
241
+ return True
242
+
243
+ # Para WEBP, verificação adicional
244
+ if mime_type.lower() == 'image/webp':
245
+ return data.startswith(b'RIFF') and b'WEBP' in data[:12]
246
+
247
+ return len(expected_sigs) == 0 # Se não tem assinatura conhecida, aceita
248
+
249
+ except Exception:
250
+ return False
251
+
252
+ # 1. VALIDAÇÃO DOS PROMPTS
253
+ try:
254
+ validator = InterpretarTextoValidator(
255
+ system_prompt=system_prompt,
256
+ user_prompt=user_prompt
257
+ )
258
+ system_prompt = validator.system_prompt
259
+ user_prompt = validator.user_prompt
260
+ except ValidationError as e:
261
+ error_details = []
262
+ for error in e.errors():
263
+ field = error['loc'][0] if error['loc'] else 'unknown'
264
+ message = error['msg']
265
+ error_details.append(f"{field}: {message}")
266
+
267
+ error_msg = f"❌ Validação dos prompts falhou: {'; '.join(error_details)}"
268
+ self.log.error(error_msg)
269
+ return {
270
+ "success": False,
271
+ "error": error_msg,
272
+ "content": None,
273
+ "input_tokens": 0,
274
+ "output_tokens": 0,
275
+ "total_tokens": 0,
276
+ "images_validated": {"total": 0, "valid": 0, "rejected": 0}
277
+ }
278
+
279
+ # 2. VALIDAÇÃO DOS ARQUIVOS/IMAGENS
280
+ images_stats = {"total": 0, "valid": 0, "rejected": 0, "rejected_files": []}
281
+ validated_images = []
282
+ validation_errors = []
283
+
284
+ if arquivos and len(arquivos) > 0:
285
+ images_stats["total"] = len(arquivos)
286
+
287
+ # Tipos de imagem suportados pelo Databricks
288
+ tipos_imagem_suportados = {
289
+ 'image/jpeg', 'image/jpg', 'image/png', 'image/gif',
290
+ 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml'
291
+ }
292
+
293
+ self.log.info(f"{jerry_prompt}🔍 Validando {len(arquivos)} arquivo(s)...")
294
+
295
+ total_size = 0
296
+
297
+ for i, arquivo in enumerate(arquivos, 1):
298
+ arquivo_name = arquivo.get("name", f"arquivo_{i}")
299
+ arquivo_mime = arquivo.get("mime_type", "").lower().strip()
300
+ arquivo_base64 = arquivo.get("base64", "")
301
+
302
+ # 2.1 Validar se tem MIME type
303
+ if not arquivo_mime:
304
+ error = f"Arquivo '{arquivo_name}': MIME type não informado"
305
+ validation_errors.append(error)
306
+ images_stats["rejected"] += 1
307
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
308
+ self.log.error(f"{jerry_prompt} ❌ {error}")
309
+ continue
310
+
311
+ # 2.2 Validar se é imagem
312
+ if not arquivo_mime.startswith('image/'):
313
+ error = f"Arquivo '{arquivo_name}': Tipo '{arquivo_mime}' não é imagem. Apenas imagens são aceitas."
314
+ validation_errors.append(error)
315
+ images_stats["rejected"] += 1
316
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
317
+ self.log.error(f"{jerry_prompt} ❌ {error}")
318
+ continue
319
+
320
+ # 2.3 Validar se é tipo de imagem suportado
321
+ if arquivo_mime not in tipos_imagem_suportados:
322
+ error = f"Imagem '{arquivo_name}': Tipo '{arquivo_mime}' não suportado. Tipos aceitos: {', '.join(sorted(tipos_imagem_suportados))}"
323
+ validation_errors.append(error)
324
+ images_stats["rejected"] += 1
325
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
326
+ self.log.error(f"{jerry_prompt} ❌ {error}")
327
+ continue
328
+
329
+ # 2.4 Validar se tem base64
330
+ if not arquivo_base64:
331
+ error = f"Imagem '{arquivo_name}': Base64 não informado"
332
+ validation_errors.append(error)
333
+ images_stats["rejected"] += 1
334
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
335
+ self.log.error(f"{jerry_prompt} ❌ {error}")
336
+ continue
337
+
338
+ # 2.5 Validar formato do base64
339
+ if not _validar_base64_formato(arquivo_base64):
340
+ error = f"Imagem '{arquivo_name}': Base64 com formato inválido"
341
+ validation_errors.append(error)
342
+ images_stats["rejected"] += 1
343
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
344
+ self.log.error(f"{jerry_prompt} ❌ {error}")
345
+ continue
346
+
347
+ # 2.6 Validar se base64 pode ser decodificado
348
+ try:
349
+ decoded_data = base64.b64decode(arquivo_base64, validate=True)
350
+ if len(decoded_data) == 0:
351
+ error = f"Imagem '{arquivo_name}': Base64 decodificado está vazio"
352
+ validation_errors.append(error)
353
+ images_stats["rejected"] += 1
354
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
355
+ self.log.error(f"{jerry_prompt} ❌ {error}")
356
+ continue
357
+ except Exception as e:
358
+ error = f"Imagem '{arquivo_name}': Erro ao decodificar base64: {str(e)}"
359
+ validation_errors.append(error)
360
+ images_stats["rejected"] += 1
361
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
362
+ self.log.error(f"{jerry_prompt} ❌ {error}")
363
+ continue
364
+
365
+ # 2.7 Validar tamanho da imagem
366
+ image_size = len(decoded_data)
367
+ total_size += image_size
368
+
369
+ # Limite por imagem: 20MB
370
+ if image_size > 20 * 1024 * 1024:
371
+ error = f"Imagem '{arquivo_name}': Tamanho muito grande ({image_size:,} bytes). Máximo: 20MB"
372
+ validation_errors.append(error)
373
+ images_stats["rejected"] += 1
374
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
375
+ self.log.error(f"{jerry_prompt} ❌ {error}")
376
+ continue
377
+
378
+ # 2.8 Validar se é realmente uma imagem (verificação básica)
379
+ if not _validar_cabecalho_imagem(decoded_data, arquivo_mime):
380
+ error = f"Imagem '{arquivo_name}': Arquivo não parece ser uma imagem válida do tipo {arquivo_mime}"
381
+ validation_errors.append(error)
382
+ images_stats["rejected"] += 1
383
+ images_stats["rejected_files"].append({"filename": arquivo_name, "error": error})
384
+ self.log.error(f"{jerry_prompt} ❌ {error}")
385
+ continue
386
+
387
+ # ✅ Imagem válida
388
+ validated_images.append(arquivo)
389
+ images_stats["valid"] += 1
390
+ self.log.info(f"{jerry_prompt} ✅ {arquivo_name} ({arquivo_mime}, {image_size:,} bytes)")
391
+
392
+ # 2.9 Validar tamanho total
393
+ if total_size > 50 * 1024 * 1024: # 50MB total
394
+ error = f"Tamanho total das imagens muito grande ({total_size:,} bytes). Máximo: 50MB"
395
+ validation_errors.append(error)
396
+ self.log.error(f"{jerry_prompt}❌ {error}")
397
+
398
+ # 2.10 Verificar se há imagens válidas
399
+ if images_stats["valid"] == 0 and images_stats["total"] > 0:
400
+ error_summary = "Nenhuma imagem válida encontrada. Erros:\n" + "\n".join(validation_errors)
401
+ self.log.error(f"{jerry_prompt}❌ {error_summary}")
402
+ return {
403
+ "success": False,
404
+ "error": error_summary,
405
+ "content": None,
406
+ "input_tokens": 0,
407
+ "output_tokens": 0,
408
+ "total_tokens": 0,
409
+ "images_validated": images_stats
410
+ }
411
+
412
+ # 2.11 Log do resumo da validação
413
+ if validation_errors:
414
+ self.log.warning(f"{jerry_prompt}⚠️ Resumo da validação:")
415
+ self.log.warning(f"{jerry_prompt} 📁 Total de arquivos: {images_stats['total']}")
416
+ self.log.warning(f"{jerry_prompt} ✅ Imagens válidas: {images_stats['valid']}")
417
+ self.log.warning(f"{jerry_prompt} ❌ Rejeitadas: {images_stats['rejected']}")
418
+ self.log.warning(f"{jerry_prompt} 📊 Tamanho total: {total_size:,} bytes")
419
+
420
+ return {
421
+ "success": False,
422
+ "error": "Algumas imagens foram rejeitadas. Veja os logs para detalhes.",
423
+ "content": None,
424
+ "input_tokens": 0,
425
+ "output_tokens": 0,
426
+ "total_tokens": 0,
427
+ "images_validated": images_stats
428
+ }
429
+ else:
430
+ self.log.info(f"{jerry_prompt}✅ Todas as {images_stats['valid']} imagem(ns) passaram na validação")
431
+
432
+ # 3. VERIFICAÇÃO DO TOKEN
433
+ if not self.is_connected:
434
+ error_msg = "Conexão com Jerry não estabelecida. Verifique as credenciais."
435
+ self.log.error(f"{jerry_prompt}❌ {error_msg}")
436
+ return {
437
+ "success": False,
438
+ "error": error_msg,
439
+ "content": None,
440
+ "input_tokens": 0,
441
+ "output_tokens": 0,
442
+ "total_tokens": 0,
443
+ "images_validated": images_stats
444
+ }
445
+
446
+ # 4. REQUISIÇÃO PARA O DATABRICKS
447
+ max_retries = 3
448
+ for attempt in range(max_retries):
449
+ try:
450
+ self.log.info(f"{jerry_prompt}🔄 Tentativa {attempt + 1}/{max_retries}")
451
+
452
+ # Mensagem do sistema
453
+ messages: List[Dict[str, Any]] = [
454
+ {"role": "system", "content": system_prompt}
455
+ ]
456
+
457
+ # Mensagem do usuário com imagens validadas
458
+ if validated_images:
459
+ self.log.info(f"{jerry_prompt}🖼️ Anexando {len(validated_images)} imagem(ns) validada(s)...")
460
+
461
+ user_content = [{"type": "text", "text": user_prompt}]
462
+
463
+ for imagem in validated_images:
464
+ image_content = {
465
+ "type": "image_url",
466
+ "image_url": {
467
+ "url": f"data:{imagem['mime_type']};base64,{imagem['base64']}"
468
+ }
469
+ }
470
+ user_content.append(image_content)
471
+
472
+ messages.append({"role": "user", "content": user_content})
473
+ else:
474
+ # Apenas texto
475
+ self.log.info(f"{jerry_prompt}📝 Enviando apenas texto (sem imagens)")
476
+ messages.append({"role": "user", "content": user_prompt})
477
+
478
+ # Requisição
479
+ self.log.info(f"{jerry_prompt}🚀 Enviando para Databricks (modelo: {self.ia_model})...")
480
+ response = self.client.chat.completions.create(
481
+ model=self.ia_model,
482
+ messages=messages
483
+ )
484
+
485
+ # ✅ SUCESSO
486
+ self.log.info(f"{jerry_prompt}✅ Resposta recebida com sucesso!")
487
+ self.log.info(f"{jerry_prompt}📊 Tokens - Entrada: {response.usage.prompt_tokens}, Saída: {response.usage.completion_tokens}, Total: {response.usage.total_tokens}")
488
+ self.log.info(f"{jerry_prompt}📄 Resposta bruta: {json.dumps(response.model_dump(), ensure_ascii=False)}")
489
+
490
+ #content_processado = self._processar_resposta(response.model_dump())
491
+ content_processado = response.model_dump()
492
+ content_processado = content_processado["choices"][0]["message"]["content"]
493
+ self.log.info(f"{jerry_prompt}📄 Conteúdo processado: {content_processado}")
494
+
495
+ return {
496
+ "success": True,
497
+ "error": None,
498
+ "content": content_processado,
499
+ "input_tokens": response.usage.prompt_tokens,
500
+ "output_tokens": response.usage.completion_tokens,
501
+ "total_tokens": response.usage.total_tokens,
502
+ "images_validated": images_stats
503
+ }
504
+
505
+ # Tratamento de exceções (mesmo código anterior)
506
+ except openai.AuthenticationError as e:
507
+ error_msg = str(e).lower()
508
+ self.log.error(f"{jerry_prompt}🔑 Erro de autenticação: {e}")
509
+
510
+ if any(keyword in error_msg for keyword in ['token inválido', 'token expirado', 'expired', 'invalid', 'unauthorized']):
511
+ if attempt < max_retries - 1:
512
+ self.log.info(f"{jerry_prompt}🔄 Token inválido/expirado detectado. Renovando...")
513
+ try:
514
+ novo_token = self._autenticar()
515
+ if novo_token:
516
+ self.token = novo_token
517
+ self.log.info(f"{jerry_prompt}✅ Token renovado com sucesso")
518
+ time.sleep(1)
519
+ continue
520
+ else:
521
+ self.log.error(f"{jerry_prompt}❌ Falha na renovação do token")
522
+ break
523
+ except Exception as auth_error:
524
+ self.log.error(f"{jerry_prompt}❌ Erro durante renovação do token: {auth_error}")
525
+ break
526
+ else:
527
+ self.log.error(f"{jerry_prompt}❌ Esgotaram as tentativas de renovação")
528
+ break
529
+ else:
530
+ self.log.error(f"{jerry_prompt}❌ Erro de autenticação não relacionado ao token")
531
+ break
532
+
533
+ except openai.RateLimitError as e:
534
+ self.log.warning(f"{jerry_prompt}⏱️ Rate limit atingido: {e}")
535
+ if attempt < max_retries - 1:
536
+ wait_time = (attempt + 1) * 5
537
+ self.log.info(f"{jerry_prompt}⏳ Aguardando {wait_time}s...")
538
+ time.sleep(wait_time)
539
+ continue
540
+ else:
541
+ break
542
+
543
+ except openai.APITimeoutError as e:
544
+ self.log.warning(f"{jerry_prompt}⏰ Timeout na requisição: {e}")
545
+ if attempt < max_retries - 1:
546
+ self.log.info(f"{jerry_prompt}🔄 Tentando novamente...")
547
+ time.sleep(2)
548
+ continue
549
+ else:
550
+ break
551
+
552
+ except openai.APIError as e:
553
+ error_msg = str(e)
554
+ self.log.error(f"{jerry_prompt}❌ Erro da API Databricks: {e}")
555
+
556
+ if any(keyword in error_msg.lower() for keyword in ['temporary', 'temporarily', 'retry', 'server error', '500', '502', '503']):
557
+ if attempt < max_retries - 1:
558
+ wait_time = (attempt + 1) * 3
559
+ self.log.info(f"{jerry_prompt}🔄 Erro temporário detectado. Tentando novamente em {wait_time}s...")
560
+ time.sleep(wait_time)
561
+ continue
562
+ else:
563
+ break
564
+ else:
565
+ break
566
+
567
+ except Exception as e:
568
+ self.log.error(f"{jerry_prompt}❌ Erro inesperado: {type(e).__name__}: {e}")
569
+
570
+ if any(keyword in str(e).lower() for keyword in ['connection', 'network', 'timeout', 'connect']):
571
+ if attempt < max_retries - 1:
572
+ self.log.info(f"{jerry_prompt}🔄 Erro de conexão detectado. Tentando novamente...")
573
+ time.sleep(3)
574
+ continue
575
+ else:
576
+ break
577
+ else:
578
+ break
579
+
580
+ # Falha após todas as tentativas
581
+ error_msg = f"Falha ao completar a requisição após {max_retries} tentativas"
582
+ self.log.error(f"{jerry_prompt}❌ {error_msg}")
583
+ return {
584
+ "success": False,
585
+ "error": error_msg,
586
+ "content": None,
587
+ "input_tokens": 0,
588
+ "output_tokens": 0,
589
+ "total_tokens": 0,
590
+ "images_validated": images_stats
591
+ }
592
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csc_cia_stne
3
- Version: 0.1.31
3
+ Version: 0.1.33
4
4
  Summary: Biblioteca do time CSC-CIA utilizada no desenvolvimento de RPAs
5
5
  License: MIT
6
6
  Keywords: karavela,csc,cia,stone,rpa,botcity,stne
@@ -34,6 +34,7 @@ Requires-Dist: botcity-maestro-sdk
34
34
  Requires-Dist: psutil
35
35
  Requires-Dist: cryptography
36
36
  Requires-Dist: PyPDF2
37
+ Requires-Dist: openai
37
38
  Requires-Dist: pycurl
38
39
  Dynamic: license-file
39
40
 
@@ -1,4 +1,4 @@
1
- csc_cia_stne/__init__.py,sha256=LITCLPqafF-VUV85wUvJ047ozLnfd82vw5AuuKXYi2s,2713
1
+ csc_cia_stne/__init__.py,sha256=OB7eYiehAS9Shm0PA5A05_uCo2DvrSKAKWw9CCrGeYE,2762
2
2
  csc_cia_stne/bc_correios.py,sha256=s2XjJ0iokMlcUv0mAy9saU3pc_G-6X8wltb_lFHIL6o,24717
3
3
  csc_cia_stne/bc_sta.py,sha256=S4EtkSEHP-wTMWRjmmSBH9XY5SnVQ1TwwZFSOE6tI2Q,29551
4
4
  csc_cia_stne/email.py,sha256=y4xyPAe6_Mga5Wf6qAsDzYgn0f-zf2KshfItlWe58z8,8481
@@ -7,6 +7,7 @@ csc_cia_stne/gcp_bigquery.py,sha256=foq8azvvv_f7uikMDslX9RcUIrx7RAS-Sn0AGW0QFQc,
7
7
  csc_cia_stne/gcp_bucket.py,sha256=vMALWiW7IoBCuJAR8bUCpOV6BuBzI9AhRRk3b72OdMk,11515
8
8
  csc_cia_stne/gcp_document_ai.py,sha256=Dzlk7YR3M_LxE0sHn-Lxz-PA1NsUZN2hgY5PyUfs0IQ,4506
9
9
  csc_cia_stne/google_drive.py,sha256=7qwx4_RPEoSJgeVI02aLYNXA7o69_Z3qONvX5bfA4V0,44500
10
+ csc_cia_stne/jerry.py,sha256=nuO3fLhBsHmqsLbvZ0thwQYVMQVDbXRBqVRzt3sq7A8,27378
10
11
  csc_cia_stne/karavela.py,sha256=jJCYX43D49gGuzmwwK6bN9XVnv2dXdp9iHnnV5H1LMQ,4794
11
12
  csc_cia_stne/logger_json.py,sha256=CXxSCOFGMymDi8XE9SKnPKjW4D0wJLqDLnxqePS26i8,3187
12
13
  csc_cia_stne/logger_rich.py,sha256=fklgkBb4rblKQd7YZ3q-eWfhGg9eflO2k2-z4pGh_yo,5201
@@ -40,8 +41,8 @@ csc_cia_stne/utilitarios/web_screen/__init__.py,sha256=5QcOPXKd95SvP2DoZiHS0gaU6
40
41
  csc_cia_stne/utilitarios/web_screen/web_screen_abstract.py,sha256=PjL8Vgfj_JdKidia7RFyCkro3avYLQu4RZRos41sh3w,3241
41
42
  csc_cia_stne/utilitarios/web_screen/web_screen_botcity.py,sha256=Xi5YJjl2pcxlX3OimqcBWRNXZEpAE7asyUjDJ4Oho5U,12297
42
43
  csc_cia_stne/utilitarios/web_screen/web_screen_selenium.py,sha256=JLIcPJE9ZX3Pd6zG6oTRMqqUAY063UzLY3ReRlxmiSM,15581
43
- csc_cia_stne-0.1.31.dist-info/licenses/LICENCE,sha256=LPGMtgKki2C3KEZP7hDhA1HBrlq5JCHkIeStUCLEMx4,1073
44
- csc_cia_stne-0.1.31.dist-info/METADATA,sha256=JXwkvdFQm1zfsj5ufVoEScJhNsedOfEWnpNZqUGNR0s,1552
45
- csc_cia_stne-0.1.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
- csc_cia_stne-0.1.31.dist-info/top_level.txt,sha256=ldo7GVv3tQx5KJvwBzdZzzQmjPys2NDVVn1rv0BOF2Q,13
47
- csc_cia_stne-0.1.31.dist-info/RECORD,,
44
+ csc_cia_stne-0.1.33.dist-info/licenses/LICENCE,sha256=LPGMtgKki2C3KEZP7hDhA1HBrlq5JCHkIeStUCLEMx4,1073
45
+ csc_cia_stne-0.1.33.dist-info/METADATA,sha256=kKfxKx0dZcWocpP9UHSwIUfvl_9AiCEWC5M4et_MAGM,1574
46
+ csc_cia_stne-0.1.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ csc_cia_stne-0.1.33.dist-info/top_level.txt,sha256=ldo7GVv3tQx5KJvwBzdZzzQmjPys2NDVVn1rv0BOF2Q,13
48
+ csc_cia_stne-0.1.33.dist-info/RECORD,,