ia-tracker-qca 0.1.0__tar.gz
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.
- ia_tracker_qca-0.1.0/LICENCE +21 -0
- ia_tracker_qca-0.1.0/MANIFEST.in +4 -0
- ia_tracker_qca-0.1.0/PKG-INFO +140 -0
- ia_tracker_qca-0.1.0/README.md +109 -0
- ia_tracker_qca-0.1.0/pyproject.toml +46 -0
- ia_tracker_qca-0.1.0/requirements.txt +4 -0
- ia_tracker_qca-0.1.0/setup.cfg +4 -0
- ia_tracker_qca-0.1.0/setup.py +6 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/__init__.py +27 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/database.py +290 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/exceptions.py +26 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/models.py +72 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/providers/__init__.py +11 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/providers/anthropic.py +81 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/providers/base.py +55 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/providers/maritaca.py +142 -0
- ia_tracker_qca-0.1.0/src/ia_cost_tracker/tracker.py +357 -0
- ia_tracker_qca-0.1.0/src/ia_tracker_qca.egg-info/PKG-INFO +140 -0
- ia_tracker_qca-0.1.0/src/ia_tracker_qca.egg-info/SOURCES.txt +24 -0
- ia_tracker_qca-0.1.0/src/ia_tracker_qca.egg-info/dependency_links.txt +1 -0
- ia_tracker_qca-0.1.0/src/ia_tracker_qca.egg-info/requires.txt +10 -0
- ia_tracker_qca-0.1.0/src/ia_tracker_qca.egg-info/top_level.txt +1 -0
- ia_tracker_qca-0.1.0/tests/test_anthropic_integration.py +201 -0
- ia_tracker_qca-0.1.0/tests/test_anthropic_real.py +180 -0
- ia_tracker_qca-0.1.0/tests/test_completo.py +134 -0
- ia_tracker_qca-0.1.0/tests/test_maritaca_integration.py +157 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elison Felipe Santos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ia_tracker_qca
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Biblioteca para rastreamento e auditoria de custos de APIs de IA
|
|
5
|
+
Author-email: Sua Equipe <contato@suaempresa.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/suaempresa/ia-cost-tracker
|
|
8
|
+
Project-URL: Documentation, https://github.com/suaempresa/ia-cost-tracker#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/suaempresa/ia-cost-tracker
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENCE
|
|
21
|
+
Requires-Dist: psycopg2-binary>=2.9.0
|
|
22
|
+
Requires-Dist: requests>=2.28.0
|
|
23
|
+
Requires-Dist: python-dotenv>=0.19.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: isort>=5.10.0; extra == "dev"
|
|
29
|
+
Requires-Dist: flake8>=4.0.0; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# IA Cost Tracker
|
|
33
|
+
|
|
34
|
+
[](https://badge.fury.io/py/ia-cost-tracker)
|
|
35
|
+
[](https://pypi.org/project/ia-cost-tracker/)
|
|
36
|
+
[](https://opensource.org/licenses/MIT)
|
|
37
|
+
[](https://pepy.tech/project/ia-cost-tracker)
|
|
38
|
+
|
|
39
|
+
Rastreie custos de APIs de IA (Maritaca, Anthropic) automaticamente.
|
|
40
|
+
|
|
41
|
+
Biblioteca Python para rastreamento e auditoria de custos de APIs de IA (Maritaca, Anthropic).
|
|
42
|
+
|
|
43
|
+
## Instalação
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install pip install ia_tracker_qca
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Uso básico (não recomendado pois demora mais por causa que busca os valores atualizados na hora!):
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from ia_cost_tracker import IATracker
|
|
54
|
+
|
|
55
|
+
# Inicializar
|
|
56
|
+
tracker = IATracker(
|
|
57
|
+
db_connection_string="postgresql://user:pass@localhost/db",
|
|
58
|
+
maritaca_api_key="sua-chave",
|
|
59
|
+
anthropic_api_key="sua-chave"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Sincronizar preços dos modelos
|
|
63
|
+
tracker.sincronizar_precos_maritaca() #essa chamada a api demora uns 10segs as vezes.
|
|
64
|
+
tracker.sincronizar_precos_anthropic()
|
|
65
|
+
|
|
66
|
+
# Registrar uma chamada
|
|
67
|
+
tracker.registrar_chamada(
|
|
68
|
+
provedor="maritaca",
|
|
69
|
+
modelo="sabia-2-medium",
|
|
70
|
+
tokens_input=150,
|
|
71
|
+
tokens_output=75,
|
|
72
|
+
aplicacao="meu-app",
|
|
73
|
+
tag_funcionalidade="chat",
|
|
74
|
+
usuario="usuario@email.com"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Obter estatísticas
|
|
78
|
+
stats = tracker.obter_estatisticas_aplicacao("meu-app")
|
|
79
|
+
print(f"Custo total: R$ {stats['custo_total']}")
|
|
80
|
+
print(f"Total de chamadas: {stats['total_chamadas']}")
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
Exemplo de uso em uma aplicação em produção:
|
|
86
|
+
```python
|
|
87
|
+
from ia_cost_tracker import IATracker
|
|
88
|
+
|
|
89
|
+
tracker = IATracker(db_connection_string=os.getenv("DB_CONNECTION_STRING"))
|
|
90
|
+
|
|
91
|
+
# Após chamar a IA
|
|
92
|
+
tracker.registrar_chamada(
|
|
93
|
+
provedor="maritaca", # ou "anthropic"
|
|
94
|
+
modelo="sabia-4",
|
|
95
|
+
tokens_input=150,
|
|
96
|
+
tokens_output=75,
|
|
97
|
+
aplicacao="meu-app",
|
|
98
|
+
tag_funcionalidade="chat",
|
|
99
|
+
usuario="user@email.com" # opcional
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Desenvolvimento:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Clonar repositório
|
|
107
|
+
git clone https://github.com/suaempresa/ia-cost-tracker
|
|
108
|
+
cd ia-cost-tracker
|
|
109
|
+
|
|
110
|
+
# Instalar em modo desenvolvimento
|
|
111
|
+
pip install -e ".[dev]"
|
|
112
|
+
|
|
113
|
+
# Rodar testes
|
|
114
|
+
pytest
|
|
115
|
+
|
|
116
|
+
# Formatar código
|
|
117
|
+
black src/
|
|
118
|
+
isort src/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Consultar Custos:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from datetime import datetime, timedelta
|
|
125
|
+
|
|
126
|
+
# Últimos 7 dias
|
|
127
|
+
data_inicio = datetime.now() - timedelta(days=7)
|
|
128
|
+
stats = tracker.obter_estatisticas_aplicacao(
|
|
129
|
+
aplicacao="meu-app",
|
|
130
|
+
data_inicio=data_inicio
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Listar modelos disponíveis
|
|
134
|
+
modelos = tracker.listar_modelos(provedor="maritaca")
|
|
135
|
+
for modelo in modelos:
|
|
136
|
+
print(f"{modelo.nome_modelo}: "
|
|
137
|
+
f"R$ {modelo.custo_input_por_1k}/1k input, "
|
|
138
|
+
f"R$ {modelo.custo_output_por_1k}/1k output")
|
|
139
|
+
|
|
140
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# IA Cost Tracker
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/ia-cost-tracker)
|
|
4
|
+
[](https://pypi.org/project/ia-cost-tracker/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://pepy.tech/project/ia-cost-tracker)
|
|
7
|
+
|
|
8
|
+
Rastreie custos de APIs de IA (Maritaca, Anthropic) automaticamente.
|
|
9
|
+
|
|
10
|
+
Biblioteca Python para rastreamento e auditoria de custos de APIs de IA (Maritaca, Anthropic).
|
|
11
|
+
|
|
12
|
+
## Instalação
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install pip install ia_tracker_qca
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Uso básico (não recomendado pois demora mais por causa que busca os valores atualizados na hora!):
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from ia_cost_tracker import IATracker
|
|
23
|
+
|
|
24
|
+
# Inicializar
|
|
25
|
+
tracker = IATracker(
|
|
26
|
+
db_connection_string="postgresql://user:pass@localhost/db",
|
|
27
|
+
maritaca_api_key="sua-chave",
|
|
28
|
+
anthropic_api_key="sua-chave"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Sincronizar preços dos modelos
|
|
32
|
+
tracker.sincronizar_precos_maritaca() #essa chamada a api demora uns 10segs as vezes.
|
|
33
|
+
tracker.sincronizar_precos_anthropic()
|
|
34
|
+
|
|
35
|
+
# Registrar uma chamada
|
|
36
|
+
tracker.registrar_chamada(
|
|
37
|
+
provedor="maritaca",
|
|
38
|
+
modelo="sabia-2-medium",
|
|
39
|
+
tokens_input=150,
|
|
40
|
+
tokens_output=75,
|
|
41
|
+
aplicacao="meu-app",
|
|
42
|
+
tag_funcionalidade="chat",
|
|
43
|
+
usuario="usuario@email.com"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Obter estatísticas
|
|
47
|
+
stats = tracker.obter_estatisticas_aplicacao("meu-app")
|
|
48
|
+
print(f"Custo total: R$ {stats['custo_total']}")
|
|
49
|
+
print(f"Total de chamadas: {stats['total_chamadas']}")
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Exemplo de uso em uma aplicação em produção:
|
|
55
|
+
```python
|
|
56
|
+
from ia_cost_tracker import IATracker
|
|
57
|
+
|
|
58
|
+
tracker = IATracker(db_connection_string=os.getenv("DB_CONNECTION_STRING"))
|
|
59
|
+
|
|
60
|
+
# Após chamar a IA
|
|
61
|
+
tracker.registrar_chamada(
|
|
62
|
+
provedor="maritaca", # ou "anthropic"
|
|
63
|
+
modelo="sabia-4",
|
|
64
|
+
tokens_input=150,
|
|
65
|
+
tokens_output=75,
|
|
66
|
+
aplicacao="meu-app",
|
|
67
|
+
tag_funcionalidade="chat",
|
|
68
|
+
usuario="user@email.com" # opcional
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Desenvolvimento:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Clonar repositório
|
|
76
|
+
git clone https://github.com/suaempresa/ia-cost-tracker
|
|
77
|
+
cd ia-cost-tracker
|
|
78
|
+
|
|
79
|
+
# Instalar em modo desenvolvimento
|
|
80
|
+
pip install -e ".[dev]"
|
|
81
|
+
|
|
82
|
+
# Rodar testes
|
|
83
|
+
pytest
|
|
84
|
+
|
|
85
|
+
# Formatar código
|
|
86
|
+
black src/
|
|
87
|
+
isort src/
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Consultar Custos:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from datetime import datetime, timedelta
|
|
94
|
+
|
|
95
|
+
# Últimos 7 dias
|
|
96
|
+
data_inicio = datetime.now() - timedelta(days=7)
|
|
97
|
+
stats = tracker.obter_estatisticas_aplicacao(
|
|
98
|
+
aplicacao="meu-app",
|
|
99
|
+
data_inicio=data_inicio
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Listar modelos disponíveis
|
|
103
|
+
modelos = tracker.listar_modelos(provedor="maritaca")
|
|
104
|
+
for modelo in modelos:
|
|
105
|
+
print(f"{modelo.nome_modelo}: "
|
|
106
|
+
f"R$ {modelo.custo_input_por_1k}/1k input, "
|
|
107
|
+
f"R$ {modelo.custo_output_por_1k}/1k output")
|
|
108
|
+
|
|
109
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ia_tracker_qca"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Biblioteca para rastreamento e auditoria de custos de APIs de IA"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Sua Equipe", email = "contato@suaempresa.com"}
|
|
12
|
+
]
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"psycopg2-binary>=2.9.0",
|
|
27
|
+
"requests>=2.28.0",
|
|
28
|
+
"python-dotenv>=0.19.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=7.0.0",
|
|
34
|
+
"pytest-cov>=3.0.0",
|
|
35
|
+
"black>=22.0.0",
|
|
36
|
+
"isort>=5.10.0",
|
|
37
|
+
"flake8>=4.0.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/suaempresa/ia-cost-tracker"
|
|
42
|
+
Documentation = "https://github.com/suaempresa/ia-cost-tracker#readme"
|
|
43
|
+
Repository = "https://github.com/suaempresa/ia-cost-tracker"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IA Cost Tracker - Biblioteca para rastreamento de custos de APIs de IA.
|
|
3
|
+
|
|
4
|
+
Esta biblioteca permite rastrear e auditar custos de chamadas a APIs de IA
|
|
5
|
+
como Maritaca e Anthropic, salvando informações detalhadas em banco de dados.
|
|
6
|
+
|
|
7
|
+
Para Maritaca: sincroniza preços via API
|
|
8
|
+
Para Anthropic: usa preços mantidos no banco de dados
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
from .tracker import IATracker
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
IATrackerError,
|
|
16
|
+
DatabaseError,
|
|
17
|
+
ProviderError,
|
|
18
|
+
ModelNotFoundError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"IATracker",
|
|
23
|
+
"IATrackerError",
|
|
24
|
+
"DatabaseError",
|
|
25
|
+
"ProviderError",
|
|
26
|
+
"ModelNotFoundError",
|
|
27
|
+
]
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""Gerenciador de conexões e operações com banco de dados."""
|
|
2
|
+
|
|
3
|
+
import psycopg2
|
|
4
|
+
from psycopg2.extras import RealDictCursor
|
|
5
|
+
from typing import List, Optional, Dict, Any
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from .models import ModeloIA, UsoToken
|
|
11
|
+
from .exceptions import DatabaseError, ModelNotFoundError
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DatabaseManager:
|
|
17
|
+
"""
|
|
18
|
+
Gerenciador de conexões e operações com PostgreSQL.
|
|
19
|
+
|
|
20
|
+
Esta classe gerencia todas as interações com o banco de dados,
|
|
21
|
+
incluindo operações CRUD para modelos e registros de uso.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, connection_string: str):
|
|
25
|
+
"""
|
|
26
|
+
Inicializa o gerenciador de banco de dados.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
connection_string: String de conexão PostgreSQL
|
|
30
|
+
(ex: "postgresql://user:pass@localhost/dbname")
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
DatabaseError: Se houver erro na conexão com o banco
|
|
34
|
+
"""
|
|
35
|
+
self.connection_string = connection_string
|
|
36
|
+
self._test_connection()
|
|
37
|
+
|
|
38
|
+
def _test_connection(self) -> None:
|
|
39
|
+
"""Testa a conexão com o banco de dados."""
|
|
40
|
+
try:
|
|
41
|
+
with self._get_connection() as conn:
|
|
42
|
+
with conn.cursor() as cur:
|
|
43
|
+
cur.execute("SELECT 1")
|
|
44
|
+
except Exception as e:
|
|
45
|
+
raise DatabaseError(f"Erro ao conectar ao banco de dados: {str(e)}")
|
|
46
|
+
|
|
47
|
+
@contextmanager
|
|
48
|
+
def _get_connection(self):
|
|
49
|
+
"""
|
|
50
|
+
Context manager para conexões com o banco.
|
|
51
|
+
|
|
52
|
+
Yields:
|
|
53
|
+
psycopg2.connection: Conexão com o banco de dados
|
|
54
|
+
"""
|
|
55
|
+
conn = None
|
|
56
|
+
try:
|
|
57
|
+
conn = psycopg2.connect(self.connection_string)
|
|
58
|
+
yield conn
|
|
59
|
+
conn.commit()
|
|
60
|
+
except Exception as e:
|
|
61
|
+
if conn:
|
|
62
|
+
conn.rollback()
|
|
63
|
+
raise DatabaseError(f"Erro na operação do banco: {str(e)}")
|
|
64
|
+
finally:
|
|
65
|
+
if conn:
|
|
66
|
+
conn.close()
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _convert_decimal_to_float(row_dict: Dict) -> Dict:
|
|
70
|
+
"""
|
|
71
|
+
Converte valores Decimal para float no dicionário.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
row_dict: Dicionário com dados do banco
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dicionário com Decimals convertidos para float
|
|
78
|
+
"""
|
|
79
|
+
converted = {}
|
|
80
|
+
for key, value in row_dict.items():
|
|
81
|
+
if isinstance(value, Decimal):
|
|
82
|
+
converted[key] = float(value)
|
|
83
|
+
else:
|
|
84
|
+
converted[key] = value
|
|
85
|
+
return converted
|
|
86
|
+
|
|
87
|
+
def buscar_modelo(self, provedor: str, nome_modelo: str) -> Optional[ModeloIA]:
|
|
88
|
+
"""
|
|
89
|
+
Busca um modelo específico no banco de dados.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
provedor: Nome do provedor (ex: 'maritaca', 'anthropic')
|
|
93
|
+
nome_modelo: Nome do modelo
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
ModeloIA se encontrado, None caso contrário
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
DatabaseError: Se houver erro na consulta
|
|
100
|
+
"""
|
|
101
|
+
query = """
|
|
102
|
+
SELECT id_modelo, provedor, nome_modelo, custo_input_por_1k,
|
|
103
|
+
custo_output_por_1k, ativo, criado_em
|
|
104
|
+
FROM dim_modelo_ia
|
|
105
|
+
WHERE provedor = %s AND nome_modelo = %s AND ativo = true
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
with self._get_connection() as conn:
|
|
109
|
+
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
110
|
+
cur.execute(query, (provedor, nome_modelo))
|
|
111
|
+
row = cur.fetchone()
|
|
112
|
+
|
|
113
|
+
if row:
|
|
114
|
+
row_dict = self._convert_decimal_to_float(dict(row))
|
|
115
|
+
return ModeloIA(**row_dict)
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def listar_modelos(self, provedor: Optional[str] = None,
|
|
119
|
+
apenas_ativos: bool = True) -> List[ModeloIA]:
|
|
120
|
+
"""
|
|
121
|
+
Lista modelos do banco de dados.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
provedor: Filtrar por provedor específico (opcional)
|
|
125
|
+
apenas_ativos: Se True, retorna apenas modelos ativos
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Lista de ModeloIA
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
DatabaseError: Se houver erro na consulta
|
|
132
|
+
"""
|
|
133
|
+
query = """
|
|
134
|
+
SELECT id_modelo, provedor, nome_modelo, custo_input_por_1k,
|
|
135
|
+
custo_output_por_1k, ativo, criado_em
|
|
136
|
+
FROM dim_modelo_ia
|
|
137
|
+
WHERE 1=1
|
|
138
|
+
"""
|
|
139
|
+
params = []
|
|
140
|
+
|
|
141
|
+
if provedor:
|
|
142
|
+
query += " AND provedor = %s"
|
|
143
|
+
params.append(provedor)
|
|
144
|
+
|
|
145
|
+
if apenas_ativos:
|
|
146
|
+
query += " AND ativo = true"
|
|
147
|
+
|
|
148
|
+
query += " ORDER BY provedor, nome_modelo"
|
|
149
|
+
|
|
150
|
+
with self._get_connection() as conn:
|
|
151
|
+
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
152
|
+
cur.execute(query, params)
|
|
153
|
+
rows = cur.fetchall()
|
|
154
|
+
modelos = []
|
|
155
|
+
for row in rows:
|
|
156
|
+
row_dict = self._convert_decimal_to_float(dict(row))
|
|
157
|
+
modelos.append(ModeloIA(**row_dict))
|
|
158
|
+
return modelos
|
|
159
|
+
|
|
160
|
+
def inserir_ou_atualizar_modelo(self, modelo: ModeloIA) -> int:
|
|
161
|
+
"""
|
|
162
|
+
Insere um novo modelo ou atualiza se já existir.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
modelo: Objeto ModeloIA a ser inserido/atualizado
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
ID do modelo inserido/atualizado
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
DatabaseError: Se houver erro na operação
|
|
172
|
+
"""
|
|
173
|
+
query = """
|
|
174
|
+
INSERT INTO dim_modelo_ia
|
|
175
|
+
(provedor, nome_modelo, custo_input_por_1k, custo_output_por_1k, ativo)
|
|
176
|
+
VALUES (%s, %s, %s, %s, %s)
|
|
177
|
+
ON CONFLICT (provedor, nome_modelo)
|
|
178
|
+
DO UPDATE SET
|
|
179
|
+
custo_input_por_1k = EXCLUDED.custo_input_por_1k,
|
|
180
|
+
custo_output_por_1k = EXCLUDED.custo_output_por_1k,
|
|
181
|
+
ativo = EXCLUDED.ativo
|
|
182
|
+
RETURNING id_modelo
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
with self._get_connection() as conn:
|
|
186
|
+
with conn.cursor() as cur:
|
|
187
|
+
cur.execute(query, (
|
|
188
|
+
modelo.provedor,
|
|
189
|
+
modelo.nome_modelo,
|
|
190
|
+
modelo.custo_input_por_1k,
|
|
191
|
+
modelo.custo_output_por_1k,
|
|
192
|
+
modelo.ativo
|
|
193
|
+
))
|
|
194
|
+
id_modelo = cur.fetchone()[0]
|
|
195
|
+
logger.info(f"Modelo {modelo.provedor}/{modelo.nome_modelo} "
|
|
196
|
+
f"inserido/atualizado com ID {id_modelo}")
|
|
197
|
+
return id_modelo
|
|
198
|
+
|
|
199
|
+
def registrar_uso(self, uso: UsoToken) -> int:
|
|
200
|
+
"""
|
|
201
|
+
Registra um uso de tokens no banco de dados.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
uso: Objeto UsoToken com os dados do uso
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
ID do registro de uso criado
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
DatabaseError: Se houver erro na inserção
|
|
211
|
+
"""
|
|
212
|
+
query = """
|
|
213
|
+
INSERT INTO fato_uso_tokens (
|
|
214
|
+
id_modelo, tokens_input, tokens_output,
|
|
215
|
+
custo_input, custo_output, tag_funcionalidade,
|
|
216
|
+
aplicacao, usuario, tempo_resposta_ms,
|
|
217
|
+
erro, mensagem_erro, timestamp_uso
|
|
218
|
+
) VALUES (
|
|
219
|
+
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
|
|
220
|
+
) RETURNING id_uso
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
with self._get_connection() as conn:
|
|
224
|
+
with conn.cursor() as cur:
|
|
225
|
+
cur.execute(query, (
|
|
226
|
+
uso.id_modelo,
|
|
227
|
+
uso.tokens_input,
|
|
228
|
+
uso.tokens_output,
|
|
229
|
+
uso.custo_input,
|
|
230
|
+
uso.custo_output,
|
|
231
|
+
uso.tag_funcionalidade,
|
|
232
|
+
uso.aplicacao,
|
|
233
|
+
uso.usuario,
|
|
234
|
+
uso.tempo_resposta_ms,
|
|
235
|
+
uso.erro,
|
|
236
|
+
uso.mensagem_erro,
|
|
237
|
+
uso.timestamp_uso
|
|
238
|
+
))
|
|
239
|
+
id_uso = cur.fetchone()[0]
|
|
240
|
+
logger.info(f"Uso registrado com ID {id_uso} - "
|
|
241
|
+
f"Custo total: R$ {uso.custo_total:.6f}")
|
|
242
|
+
return id_uso
|
|
243
|
+
|
|
244
|
+
def buscar_custos_por_aplicacao(self, aplicacao: str,
|
|
245
|
+
data_inicio: Optional[Any] = None,
|
|
246
|
+
data_fim: Optional[Any] = None) -> Dict[str, Any]:
|
|
247
|
+
"""
|
|
248
|
+
Busca estatísticas de custo para uma aplicação específica.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
aplicacao: Nome da aplicação
|
|
252
|
+
data_inicio: Data inicial do período (opcional)
|
|
253
|
+
data_fim: Data final do período (opcional)
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Dicionário com estatísticas de uso e custo
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
DatabaseError: Se houver erro na consulta
|
|
260
|
+
"""
|
|
261
|
+
query = """
|
|
262
|
+
SELECT
|
|
263
|
+
COUNT(*) as total_chamadas,
|
|
264
|
+
SUM(tokens_input) as total_tokens_input,
|
|
265
|
+
SUM(tokens_output) as total_tokens_output,
|
|
266
|
+
SUM(tokens_total) as total_tokens,
|
|
267
|
+
SUM(custo_total) as custo_total,
|
|
268
|
+
AVG(custo_total) as custo_medio,
|
|
269
|
+
AVG(tempo_resposta_ms) as tempo_medio_resposta_ms,
|
|
270
|
+
SUM(CASE WHEN erro = true THEN 1 ELSE 0 END) as total_erros
|
|
271
|
+
FROM fato_uso_tokens
|
|
272
|
+
WHERE aplicacao = %s
|
|
273
|
+
"""
|
|
274
|
+
params = [aplicacao]
|
|
275
|
+
|
|
276
|
+
if data_inicio:
|
|
277
|
+
query += " AND timestamp_uso >= %s"
|
|
278
|
+
params.append(data_inicio)
|
|
279
|
+
|
|
280
|
+
if data_fim:
|
|
281
|
+
query += " AND timestamp_uso <= %s"
|
|
282
|
+
params.append(data_fim)
|
|
283
|
+
|
|
284
|
+
with self._get_connection() as conn:
|
|
285
|
+
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
286
|
+
cur.execute(query, params)
|
|
287
|
+
resultado = cur.fetchone()
|
|
288
|
+
if resultado:
|
|
289
|
+
return self._convert_decimal_to_float(dict(resultado))
|
|
290
|
+
return {}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Exceções personalizadas para o IA Cost Tracker."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IATrackerError(Exception):
|
|
5
|
+
"""Exceção base para erros do IA Cost Tracker."""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DatabaseError(IATrackerError):
|
|
10
|
+
"""Exceção para erros relacionados ao banco de dados."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProviderError(IATrackerError):
|
|
15
|
+
"""Exceção para erros relacionados aos provedores de IA."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ModelNotFoundError(IATrackerError):
|
|
20
|
+
"""Exceção quando um modelo não é encontrado no banco de dados."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConfigurationError(IATrackerError):
|
|
25
|
+
"""Exceção para erros de configuração."""
|
|
26
|
+
pass
|