iatoolkit 0.7.5__py3-none-any.whl → 0.7.6__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.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- {iatoolkit-0.7.5.dist-info → iatoolkit-0.7.6.dist-info}/METADATA +1 -1
- iatoolkit-0.7.6.dist-info/RECORD +80 -0
- {iatoolkit-0.7.5.dist-info → iatoolkit-0.7.6.dist-info}/top_level.txt +3 -0
- infra/__init__.py +5 -0
- infra/call_service.py +140 -0
- infra/connectors/__init__.py +5 -0
- infra/connectors/file_connector.py +17 -0
- infra/connectors/file_connector_factory.py +57 -0
- infra/connectors/google_cloud_storage_connector.py +53 -0
- infra/connectors/google_drive_connector.py +68 -0
- infra/connectors/local_file_connector.py +46 -0
- infra/connectors/s3_connector.py +33 -0
- infra/gemini_adapter.py +356 -0
- infra/google_chat_app.py +57 -0
- infra/llm_client.py +430 -0
- infra/llm_proxy.py +139 -0
- infra/llm_response.py +40 -0
- infra/mail_app.py +145 -0
- infra/openai_adapter.py +90 -0
- infra/redis_session_manager.py +76 -0
- repositories/__init__.py +5 -0
- repositories/database_manager.py +95 -0
- repositories/document_repo.py +33 -0
- repositories/llm_query_repo.py +91 -0
- repositories/models.py +309 -0
- repositories/profile_repo.py +118 -0
- repositories/tasks_repo.py +52 -0
- repositories/vs_repo.py +139 -0
- views/__init__.py +5 -0
- views/change_password_view.py +91 -0
- views/chat_token_request_view.py +98 -0
- views/chat_view.py +51 -0
- views/download_file_view.py +58 -0
- views/external_chat_login_view.py +88 -0
- views/external_login_view.py +40 -0
- views/file_store_view.py +58 -0
- views/forgot_password_view.py +64 -0
- views/history_view.py +57 -0
- views/home_view.py +34 -0
- views/llmquery_view.py +65 -0
- views/login_view.py +60 -0
- views/prompt_view.py +37 -0
- views/signup_view.py +87 -0
- views/tasks_review_view.py +83 -0
- views/tasks_view.py +98 -0
- views/user_feedback_view.py +74 -0
- views/verify_user_view.py +55 -0
- iatoolkit-0.7.5.dist-info/RECORD +0 -36
- {iatoolkit-0.7.5.dist-info → iatoolkit-0.7.6.dist-info}/WHEEL +0 -0
infra/mail_app.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import sib_api_v3_sdk
|
|
7
|
+
from sib_api_v3_sdk.rest import ApiException
|
|
8
|
+
from common.exceptions import IAToolkitException
|
|
9
|
+
import os
|
|
10
|
+
import base64
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
MAX_ATTACH_BYTES = int(os.getenv("BREVO_MAX_ATTACH_BYTES", str(5 * 1024 * 1024))) # 5MB seguro
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MailApp:
|
|
17
|
+
def __init__(self,):
|
|
18
|
+
self.configuration = sib_api_v3_sdk.Configuration()
|
|
19
|
+
self.configuration.api_key['api-key'] = os.getenv('BREVO_API_KEY')
|
|
20
|
+
self.mail_api = sib_api_v3_sdk.TransactionalEmailsApi(sib_api_v3_sdk.ApiClient(self.configuration))
|
|
21
|
+
self.sender = {"email": "ia@iatoolkit.com", "name": "IA Toolkit"}
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def _strip_data_url_prefix(b64: str) -> str:
|
|
25
|
+
if not isinstance(b64, str):
|
|
26
|
+
return b64
|
|
27
|
+
i = b64.find("base64,")
|
|
28
|
+
return b64[i + 7:] if i != -1 else b64
|
|
29
|
+
|
|
30
|
+
def _normalize_attachments(self, attachments: list[dict] | None):
|
|
31
|
+
if not attachments:
|
|
32
|
+
return None
|
|
33
|
+
sdk_attachments = []
|
|
34
|
+
for idx, a in enumerate(attachments, start=1):
|
|
35
|
+
# 1) claves obligatorias
|
|
36
|
+
if "filename" not in a:
|
|
37
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
38
|
+
f"Adjunto #{idx} inválido: falta 'filename'")
|
|
39
|
+
if "content" not in a:
|
|
40
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
41
|
+
f"Adjunto '{a.get('filename', '(sin nombre)')}' inválido: falta 'content'")
|
|
42
|
+
|
|
43
|
+
name = a["filename"]
|
|
44
|
+
content_b64 = a["content"]
|
|
45
|
+
|
|
46
|
+
# 2) quitar prefijo data URL si vino así
|
|
47
|
+
content_b64 = self._strip_data_url_prefix(content_b64)
|
|
48
|
+
|
|
49
|
+
# 3) validar base64 (y que no esté vacío)
|
|
50
|
+
try:
|
|
51
|
+
raw = base64.b64decode(content_b64, validate=True)
|
|
52
|
+
except Exception:
|
|
53
|
+
logging.error("Adjunto '%s' con base64 inválido (primeros 16 chars: %r)",
|
|
54
|
+
name, str(content_b64)[:16])
|
|
55
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
56
|
+
f"Adjunto '{name}' trae base64 inválido")
|
|
57
|
+
|
|
58
|
+
if not raw:
|
|
59
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
60
|
+
f"Adjunto '{name}' está vacío")
|
|
61
|
+
|
|
62
|
+
# 4) volver a base64 limpio (sin prefijos, sin espacios)
|
|
63
|
+
clean_b64 = base64.b64encode(raw).decode("utf-8")
|
|
64
|
+
|
|
65
|
+
# 5) construir objeto del SDK
|
|
66
|
+
sdk_attachments.append(
|
|
67
|
+
sib_api_v3_sdk.SendSmtpEmailAttachment(
|
|
68
|
+
name=name,
|
|
69
|
+
content=clean_b64
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return sdk_attachments
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def send_email(self,
|
|
76
|
+
to: str,
|
|
77
|
+
subject: str,
|
|
78
|
+
body: str,
|
|
79
|
+
sender: dict = None,
|
|
80
|
+
attachments: list[dict] = None):
|
|
81
|
+
if not sender:
|
|
82
|
+
sender = self.sender
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
sdk_attachments = self._normalize_attachments(attachments)
|
|
86
|
+
email = sib_api_v3_sdk.SendSmtpEmail(
|
|
87
|
+
to=[{"email": to}],
|
|
88
|
+
sender=sender,
|
|
89
|
+
subject=subject,
|
|
90
|
+
html_content=body,
|
|
91
|
+
attachment=sdk_attachments
|
|
92
|
+
)
|
|
93
|
+
api_response = self.mail_api.send_transac_email(email)
|
|
94
|
+
|
|
95
|
+
# Validación de respuesta
|
|
96
|
+
message_id = getattr(api_response, "message_id", None) or getattr(api_response, "messageId", None)
|
|
97
|
+
message_ids = getattr(api_response, "message_ids", None) or getattr(api_response, "messageIds", None)
|
|
98
|
+
if not ((isinstance(message_id, str) and message_id.strip()) or
|
|
99
|
+
(isinstance(message_ids, (list, tuple)) and len(message_ids) > 0)):
|
|
100
|
+
logging.error("MAIL ERROR: Respuesta sin message_id(s): %r", api_response)
|
|
101
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
102
|
+
"Brevo no retornó message_id; el envío podría haber fallado.")
|
|
103
|
+
|
|
104
|
+
return api_response
|
|
105
|
+
|
|
106
|
+
except ApiException as e:
|
|
107
|
+
logging.exception("MAIL ERROR (ApiException): status=%s reason=%s body=%s",
|
|
108
|
+
getattr(e, "status", None), getattr(e, "reason", None), getattr(e, "body", None))
|
|
109
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
110
|
+
f"Error Brevo (status={getattr(e, 'status', 'N/A')}): {getattr(e, 'reason', str(e))}") from e
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logging.exception("MAIL ERROR: %s", str(e))
|
|
113
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
114
|
+
f"No se pudo enviar correo: {str(e)}") from e
|
|
115
|
+
''''
|
|
116
|
+
def send_template_email(self,
|
|
117
|
+
subject: str,
|
|
118
|
+
recipients: list,
|
|
119
|
+
template_name: str,
|
|
120
|
+
context: dict,
|
|
121
|
+
sender=None):
|
|
122
|
+
try:
|
|
123
|
+
# Renderiza el template con el contexto proporcionado
|
|
124
|
+
with self.app.app_context():
|
|
125
|
+
html_message = render_template(template_name, **context)
|
|
126
|
+
|
|
127
|
+
# Crea el mensaje
|
|
128
|
+
msg = Message(
|
|
129
|
+
subject=subject,
|
|
130
|
+
recipients=recipients,
|
|
131
|
+
html=html_message,
|
|
132
|
+
sender=sender or self.app.config.get('MAIL_DEFAULT_SENDER')
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Envía el correo
|
|
136
|
+
# self.send_brevo_email(msg)
|
|
137
|
+
pass
|
|
138
|
+
except jinja2.exceptions.TemplateNotFound:
|
|
139
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
140
|
+
f"Error: No se encontró el template '{template_name}'.")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
143
|
+
f'No se pudo enviar correo: {str(e)}') from e
|
|
144
|
+
|
|
145
|
+
'''
|
infra/openai_adapter.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
from infra.llm_response import LLMResponse, ToolCall, Usage
|
|
9
|
+
from common.exceptions import IAToolkitException
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenAIAdapter:
|
|
13
|
+
"""Adaptador para la API de OpenAI"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, openai_client):
|
|
16
|
+
self.client = openai_client
|
|
17
|
+
|
|
18
|
+
def create_response(self,
|
|
19
|
+
model: str,
|
|
20
|
+
input: List[Dict],
|
|
21
|
+
previous_response_id: Optional[str] = None,
|
|
22
|
+
context_history: Optional[List[Dict]] = None,
|
|
23
|
+
tools: Optional[List[Dict]] = None,
|
|
24
|
+
text: Optional[Dict] = None,
|
|
25
|
+
reasoning: Optional[Dict] = None,
|
|
26
|
+
tool_choice: str = "auto") -> LLMResponse:
|
|
27
|
+
"""Llamada a la API de OpenAI y mapeo a estructura común"""
|
|
28
|
+
try:
|
|
29
|
+
# Preparar parámetros para OpenAI
|
|
30
|
+
params = {
|
|
31
|
+
'model': model,
|
|
32
|
+
'input': input
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if previous_response_id:
|
|
36
|
+
params['previous_response_id'] = previous_response_id
|
|
37
|
+
if tools:
|
|
38
|
+
params['tools'] = tools
|
|
39
|
+
if text:
|
|
40
|
+
params['text'] = text
|
|
41
|
+
if reasoning:
|
|
42
|
+
params['reasoning'] = reasoning
|
|
43
|
+
if tool_choice != "auto":
|
|
44
|
+
params['tool_choice'] = tool_choice
|
|
45
|
+
|
|
46
|
+
# Llamar a la API de OpenAI
|
|
47
|
+
openai_response = self.client.responses.create(**params)
|
|
48
|
+
|
|
49
|
+
# Mapear la respuesta a estructura común
|
|
50
|
+
return self._map_openai_response(openai_response)
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
error_message = f"Error calling OpenAI API: {str(e)}"
|
|
54
|
+
logging.error(error_message)
|
|
55
|
+
|
|
56
|
+
# En caso de error de contexto
|
|
57
|
+
if "context_length_exceeded" in str(e):
|
|
58
|
+
error_message = 'Tu consulta supera el limite de contexto, sale e ingresa de nuevo a IAToolkit'
|
|
59
|
+
|
|
60
|
+
raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
|
|
61
|
+
|
|
62
|
+
def _map_openai_response(self, openai_response) -> LLMResponse:
|
|
63
|
+
"""Mapear respuesta de OpenAI a estructura común"""
|
|
64
|
+
# Mapear tool calls
|
|
65
|
+
tool_calls = []
|
|
66
|
+
if hasattr(openai_response, 'output') and openai_response.output:
|
|
67
|
+
for tool_call in openai_response.output:
|
|
68
|
+
if hasattr(tool_call, 'type') and tool_call.type == "function_call":
|
|
69
|
+
tool_calls.append(ToolCall(
|
|
70
|
+
call_id=getattr(tool_call, 'call_id', ''),
|
|
71
|
+
type=tool_call.type,
|
|
72
|
+
name=getattr(tool_call, 'name', ''),
|
|
73
|
+
arguments=getattr(tool_call, 'arguments', '{}')
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
# Mapear usage
|
|
77
|
+
usage = Usage(
|
|
78
|
+
input_tokens=openai_response.usage.input_tokens if openai_response.usage else 0,
|
|
79
|
+
output_tokens=openai_response.usage.output_tokens if openai_response.usage else 0,
|
|
80
|
+
total_tokens=openai_response.usage.total_tokens if openai_response.usage else 0
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return LLMResponse(
|
|
84
|
+
id=openai_response.id,
|
|
85
|
+
model=openai_response.model,
|
|
86
|
+
status=openai_response.status,
|
|
87
|
+
output_text=getattr(openai_response, 'output_text', ''),
|
|
88
|
+
output=tool_calls,
|
|
89
|
+
usage=usage
|
|
90
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import redis
|
|
9
|
+
import json
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RedisSessionManager:
|
|
14
|
+
"""
|
|
15
|
+
SessionManager que usa Redis directamente para datos persistentes como llm_history.
|
|
16
|
+
Separado de Flask session para tener control total sobre el ciclo de vida de los datos.
|
|
17
|
+
"""
|
|
18
|
+
_client = None
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def _get_client(cls):
|
|
22
|
+
if cls._client is None:
|
|
23
|
+
# Usar exactamente los mismos parámetros que Flask-Session
|
|
24
|
+
url = urlparse(os.environ.get("REDIS_URL"))
|
|
25
|
+
cls._client = redis.Redis(
|
|
26
|
+
host=url.hostname,
|
|
27
|
+
port=url.port,
|
|
28
|
+
password=url.password,
|
|
29
|
+
ssl=(url.scheme == "rediss"),
|
|
30
|
+
ssl_cert_reqs=None,
|
|
31
|
+
decode_responses=True # Importante para strings
|
|
32
|
+
)
|
|
33
|
+
# verify connection
|
|
34
|
+
cls._client.ping()
|
|
35
|
+
info = cls._client.info(section="server")
|
|
36
|
+
db = cls._client.connection_pool.connection_kwargs.get('db', 0)
|
|
37
|
+
return cls._client
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def set(cls, key: str, value: str, ex: int = None):
|
|
41
|
+
client = cls._get_client()
|
|
42
|
+
result = client.set(key, value, ex=ex)
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def get(cls, key: str, default: str = ""):
|
|
47
|
+
client = cls._get_client()
|
|
48
|
+
value = client.get(key)
|
|
49
|
+
result = value if value is not None else default
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def remove(cls, key: str):
|
|
54
|
+
client = cls._get_client()
|
|
55
|
+
result = client.delete(key)
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def set_json(cls, key: str, value: dict, ex: int = None):
|
|
60
|
+
json_str = json.dumps(value)
|
|
61
|
+
return cls.set(key, json_str, ex=ex)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def get_json(cls, key: str, default: dict = None):
|
|
65
|
+
if default is None:
|
|
66
|
+
default = {}
|
|
67
|
+
|
|
68
|
+
json_str = cls.get(key, "")
|
|
69
|
+
if not json_str:
|
|
70
|
+
return default
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
return json.loads(json_str)
|
|
74
|
+
except json.JSONDecodeError:
|
|
75
|
+
logging.warning(f"[RedisSessionManager] Invalid JSON in key '{key}': {json_str}")
|
|
76
|
+
return default
|
repositories/__init__.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
# database_manager.py
|
|
7
|
+
from sqlalchemy import create_engine, event, inspect
|
|
8
|
+
from sqlalchemy.orm import sessionmaker, scoped_session
|
|
9
|
+
from sqlalchemy.engine.url import make_url
|
|
10
|
+
from repositories.models import Base
|
|
11
|
+
from injector import inject
|
|
12
|
+
from pgvector.psycopg2 import register_vector
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatabaseManager:
|
|
16
|
+
@inject
|
|
17
|
+
def __init__(self, database_url: str, register_pgvector: bool = True):
|
|
18
|
+
"""
|
|
19
|
+
Inicializa el gestor de la base de datos.
|
|
20
|
+
:param database_url: URL de la base de datos.
|
|
21
|
+
:param echo: Si True, habilita logs de SQL.
|
|
22
|
+
"""
|
|
23
|
+
self.url = make_url(database_url)
|
|
24
|
+
self._engine = create_engine(database_url, echo=False)
|
|
25
|
+
self.SessionFactory = sessionmaker(bind=self._engine)
|
|
26
|
+
self.scoped_session = scoped_session(self.SessionFactory)
|
|
27
|
+
|
|
28
|
+
# REGISTRAR pgvector para cada nueva conexión solo en postgres
|
|
29
|
+
if register_pgvector and self.url.get_backend_name() == 'postgresql':
|
|
30
|
+
event.listen(self._engine, 'connect', self.on_connect)
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def on_connect(dbapi_connection, connection_record):
|
|
34
|
+
"""
|
|
35
|
+
Esta función se ejecuta cada vez que se establece una conexión.
|
|
36
|
+
dbapi_connection es la conexión psycopg2 real.
|
|
37
|
+
"""
|
|
38
|
+
register_vector(dbapi_connection)
|
|
39
|
+
|
|
40
|
+
def get_session(self):
|
|
41
|
+
return self.scoped_session()
|
|
42
|
+
|
|
43
|
+
def get_connection(self):
|
|
44
|
+
return self._engine.connect()
|
|
45
|
+
|
|
46
|
+
def get_engine(self):
|
|
47
|
+
return self._engine
|
|
48
|
+
|
|
49
|
+
def create_all(self):
|
|
50
|
+
Base.metadata.create_all(self._engine)
|
|
51
|
+
|
|
52
|
+
def drop_all(self):
|
|
53
|
+
Base.metadata.drop_all(self._engine)
|
|
54
|
+
|
|
55
|
+
def remove_session(self):
|
|
56
|
+
self.scoped_session.remove()
|
|
57
|
+
|
|
58
|
+
def get_table_schema(self,
|
|
59
|
+
table_name: str,
|
|
60
|
+
schema_name: str | None = None,
|
|
61
|
+
exclude_columns: list[str] | None = None) -> str:
|
|
62
|
+
inspector = inspect(self._engine)
|
|
63
|
+
|
|
64
|
+
if table_name not in inspector.get_table_names():
|
|
65
|
+
raise RuntimeError(f"La tabla '{table_name}' no existe en la BD.")
|
|
66
|
+
|
|
67
|
+
if exclude_columns is None:
|
|
68
|
+
exclude_columns = []
|
|
69
|
+
|
|
70
|
+
# get all thre table columns
|
|
71
|
+
columns = inspector.get_columns(table_name)
|
|
72
|
+
|
|
73
|
+
# construct a json dictionary with the table definition
|
|
74
|
+
json_dict = {
|
|
75
|
+
"table": table_name,
|
|
76
|
+
"description": f"Definición de la tabla {table_name}.",
|
|
77
|
+
"fields": []
|
|
78
|
+
}
|
|
79
|
+
if schema_name:
|
|
80
|
+
json_dict["description"] += f"Los detalles de cada campo están en el objeto **`{schema_name}`**."
|
|
81
|
+
|
|
82
|
+
# now add every column to the json dictionary
|
|
83
|
+
for col in columns:
|
|
84
|
+
name = col["name"]
|
|
85
|
+
|
|
86
|
+
# omit the excluded columns.
|
|
87
|
+
if name in exclude_columns:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
json_dict["fields"].append({
|
|
91
|
+
"name": name,
|
|
92
|
+
"type": str(col["type"]),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
return "\n\n" + str(json_dict)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from repositories.models import Document
|
|
7
|
+
from injector import inject
|
|
8
|
+
from repositories.database_manager import DatabaseManager
|
|
9
|
+
from common.exceptions import IAToolkitException
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DocumentRepo:
|
|
13
|
+
@inject
|
|
14
|
+
def __init__(self, db_manager: DatabaseManager):
|
|
15
|
+
self.session = db_manager.get_session()
|
|
16
|
+
|
|
17
|
+
def insert(self,new_document: Document):
|
|
18
|
+
self.session.add(new_document)
|
|
19
|
+
self.session.commit()
|
|
20
|
+
return new_document
|
|
21
|
+
|
|
22
|
+
def get(self, company_id, filename: str ) -> Document:
|
|
23
|
+
if not company_id or not filename:
|
|
24
|
+
raise IAToolkitException(IAToolkitException.ErrorType.PARAM_NOT_FILLED,
|
|
25
|
+
'Falta empresa o filename')
|
|
26
|
+
|
|
27
|
+
return self.session.query(Document).filter_by(company_id=company_id, filename=filename).first()
|
|
28
|
+
|
|
29
|
+
def get_by_id(self, document_id: int) -> Document:
|
|
30
|
+
if not document_id:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
return self.session.query(Document).filter_by(id=document_id).first()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from repositories.models import LLMQuery, Function, Company, Prompt, PromptCategory
|
|
7
|
+
from injector import inject
|
|
8
|
+
from repositories.database_manager import DatabaseManager
|
|
9
|
+
from sqlalchemy import or_
|
|
10
|
+
|
|
11
|
+
class LLMQueryRepo:
|
|
12
|
+
@inject
|
|
13
|
+
def __init__(self, db_manager: DatabaseManager):
|
|
14
|
+
self.session = db_manager.get_session()
|
|
15
|
+
|
|
16
|
+
def add_query(self, query: LLMQuery):
|
|
17
|
+
self.session.add(query)
|
|
18
|
+
self.session.commit()
|
|
19
|
+
return query
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_company_functions(self, company: Company) -> list[Function]:
|
|
23
|
+
return (
|
|
24
|
+
self.session.query(Function)
|
|
25
|
+
.filter(
|
|
26
|
+
Function.is_active.is_(True),
|
|
27
|
+
or_(
|
|
28
|
+
Function.company_id == company.id,
|
|
29
|
+
Function.system_function.is_(True)
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
.all()
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def create_or_update_function(self, new_function: Function):
|
|
36
|
+
function = self.session.query(Function).filter_by(company_id=new_function.company_id,
|
|
37
|
+
name=new_function.name).first()
|
|
38
|
+
if function:
|
|
39
|
+
function.description = new_function.description
|
|
40
|
+
function.parameters = new_function.parameters
|
|
41
|
+
function.system_function = new_function.system_function
|
|
42
|
+
else:
|
|
43
|
+
self.session.add(new_function)
|
|
44
|
+
function = new_function
|
|
45
|
+
|
|
46
|
+
self.session.commit()
|
|
47
|
+
return function
|
|
48
|
+
|
|
49
|
+
def create_or_update_prompt(self, new_prompt: Prompt):
|
|
50
|
+
prompt = self.session.query(Prompt).filter_by(company_id=new_prompt.company_id,
|
|
51
|
+
name=new_prompt.name).first()
|
|
52
|
+
if prompt:
|
|
53
|
+
prompt.category_id = new_prompt.category_id
|
|
54
|
+
prompt.description = new_prompt.description
|
|
55
|
+
prompt.order = new_prompt.order
|
|
56
|
+
prompt.active = new_prompt.active
|
|
57
|
+
prompt.is_system_prompt = new_prompt.is_system_prompt
|
|
58
|
+
prompt.filename = new_prompt.filename
|
|
59
|
+
prompt.custom_fields = new_prompt.custom_fields
|
|
60
|
+
else:
|
|
61
|
+
self.session.add(new_prompt)
|
|
62
|
+
prompt = new_prompt
|
|
63
|
+
|
|
64
|
+
self.session.commit()
|
|
65
|
+
return prompt
|
|
66
|
+
|
|
67
|
+
def create_or_update_prompt_category(self, new_category: PromptCategory):
|
|
68
|
+
category = self.session.query(PromptCategory).filter_by(company_id=new_category.company_id,
|
|
69
|
+
name=new_category.name).first()
|
|
70
|
+
if category:
|
|
71
|
+
category.order = new_category.order
|
|
72
|
+
else:
|
|
73
|
+
self.session.add(new_category)
|
|
74
|
+
category = new_category
|
|
75
|
+
|
|
76
|
+
self.session.commit()
|
|
77
|
+
return category
|
|
78
|
+
|
|
79
|
+
def get_history(self, company: Company, user_identifier: str) -> list[LLMQuery]:
|
|
80
|
+
return self.session.query(LLMQuery).filter(
|
|
81
|
+
LLMQuery.user_identifier == user_identifier,
|
|
82
|
+
).filter_by(company_id=company.id).order_by(LLMQuery.created_at.desc()).limit(100).all()
|
|
83
|
+
|
|
84
|
+
def get_prompts(self, company: Company) -> list[Prompt]:
|
|
85
|
+
return self.session.query(Prompt).filter_by(company_id=company.id, is_system_prompt=False).all()
|
|
86
|
+
|
|
87
|
+
def get_system_prompts(self) -> list[Prompt]:
|
|
88
|
+
return self.session.query(Prompt).filter_by(is_system_prompt=True, active=True).order_by(Prompt.order).all()
|
|
89
|
+
|
|
90
|
+
def get_prompt_by_name(self, company: Company, prompt_name: str):
|
|
91
|
+
return self.session.query(Prompt).filter_by(company_id=company.id, name=prompt_name).first()
|