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 +2 -0
- csc_cia_stne/jerry.py +592 -0
- {csc_cia_stne-0.1.31.dist-info → csc_cia_stne-0.1.33.dist-info}/METADATA +2 -1
- {csc_cia_stne-0.1.31.dist-info → csc_cia_stne-0.1.33.dist-info}/RECORD +7 -6
- {csc_cia_stne-0.1.31.dist-info → csc_cia_stne-0.1.33.dist-info}/WHEEL +0 -0
- {csc_cia_stne-0.1.31.dist-info → csc_cia_stne-0.1.33.dist-info}/licenses/LICENCE +0 -0
- {csc_cia_stne-0.1.31.dist-info → csc_cia_stne-0.1.33.dist-info}/top_level.txt +0 -0
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.
|
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=
|
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.
|
44
|
-
csc_cia_stne-0.1.
|
45
|
-
csc_cia_stne-0.1.
|
46
|
-
csc_cia_stne-0.1.
|
47
|
-
csc_cia_stne-0.1.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|