iatoolkit 0.71.4__py3-none-any.whl → 1.4.2__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.
Files changed (114) hide show
  1. iatoolkit/__init__.py +19 -7
  2. iatoolkit/base_company.py +1 -71
  3. iatoolkit/cli_commands.py +9 -21
  4. iatoolkit/common/exceptions.py +2 -0
  5. iatoolkit/common/interfaces/__init__.py +0 -0
  6. iatoolkit/common/interfaces/asset_storage.py +34 -0
  7. iatoolkit/common/interfaces/database_provider.py +38 -0
  8. iatoolkit/common/model_registry.py +159 -0
  9. iatoolkit/common/routes.py +53 -32
  10. iatoolkit/common/util.py +17 -12
  11. iatoolkit/company_registry.py +55 -14
  12. iatoolkit/{iatoolkit.py → core.py} +102 -72
  13. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  14. iatoolkit/infra/llm_providers/__init__.py +0 -0
  15. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  16. iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
  17. iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
  18. iatoolkit/infra/llm_proxy.py +235 -134
  19. iatoolkit/infra/llm_response.py +5 -0
  20. iatoolkit/locales/en.yaml +134 -4
  21. iatoolkit/locales/es.yaml +293 -162
  22. iatoolkit/repositories/database_manager.py +92 -22
  23. iatoolkit/repositories/document_repo.py +7 -0
  24. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  25. iatoolkit/repositories/llm_query_repo.py +36 -22
  26. iatoolkit/repositories/models.py +86 -95
  27. iatoolkit/repositories/profile_repo.py +64 -13
  28. iatoolkit/repositories/vs_repo.py +31 -28
  29. iatoolkit/services/auth_service.py +1 -1
  30. iatoolkit/services/branding_service.py +1 -1
  31. iatoolkit/services/company_context_service.py +96 -39
  32. iatoolkit/services/configuration_service.py +329 -67
  33. iatoolkit/services/dispatcher_service.py +51 -227
  34. iatoolkit/services/document_service.py +10 -1
  35. iatoolkit/services/embedding_service.py +9 -6
  36. iatoolkit/services/excel_service.py +50 -2
  37. iatoolkit/services/file_processor_service.py +0 -5
  38. iatoolkit/services/history_manager_service.py +208 -0
  39. iatoolkit/services/jwt_service.py +1 -1
  40. iatoolkit/services/knowledge_base_service.py +412 -0
  41. iatoolkit/services/language_service.py +8 -2
  42. iatoolkit/services/license_service.py +82 -0
  43. iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
  44. iatoolkit/services/load_documents_service.py +18 -47
  45. iatoolkit/services/mail_service.py +171 -25
  46. iatoolkit/services/profile_service.py +69 -36
  47. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
  48. iatoolkit/services/query_service.py +229 -203
  49. iatoolkit/services/sql_service.py +116 -34
  50. iatoolkit/services/tool_service.py +246 -0
  51. iatoolkit/services/user_feedback_service.py +18 -6
  52. iatoolkit/services/user_session_context_service.py +121 -51
  53. iatoolkit/static/images/iatoolkit_core.png +0 -0
  54. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  55. iatoolkit/static/js/chat_feedback_button.js +1 -1
  56. iatoolkit/static/js/chat_help_content.js +4 -4
  57. iatoolkit/static/js/chat_main.js +61 -9
  58. iatoolkit/static/js/chat_model_selector.js +227 -0
  59. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  60. iatoolkit/static/js/chat_reload_button.js +4 -1
  61. iatoolkit/static/styles/chat_iatoolkit.css +59 -3
  62. iatoolkit/static/styles/chat_public.css +28 -0
  63. iatoolkit/static/styles/documents.css +598 -0
  64. iatoolkit/static/styles/landing_page.css +223 -7
  65. iatoolkit/static/styles/llm_output.css +34 -1
  66. iatoolkit/system_prompts/__init__.py +0 -0
  67. iatoolkit/system_prompts/query_main.prompt +28 -3
  68. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  69. iatoolkit/templates/_company_header.html +30 -5
  70. iatoolkit/templates/_login_widget.html +3 -3
  71. iatoolkit/templates/base.html +13 -0
  72. iatoolkit/templates/chat.html +45 -3
  73. iatoolkit/templates/forgot_password.html +3 -2
  74. iatoolkit/templates/onboarding_shell.html +1 -2
  75. iatoolkit/templates/signup.html +3 -0
  76. iatoolkit/views/base_login_view.py +8 -3
  77. iatoolkit/views/change_password_view.py +1 -1
  78. iatoolkit/views/chat_view.py +76 -0
  79. iatoolkit/views/forgot_password_view.py +9 -4
  80. iatoolkit/views/history_api_view.py +3 -3
  81. iatoolkit/views/home_view.py +4 -2
  82. iatoolkit/views/init_context_api_view.py +1 -1
  83. iatoolkit/views/llmquery_api_view.py +4 -3
  84. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  85. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
  86. iatoolkit/views/login_view.py +25 -8
  87. iatoolkit/views/logout_api_view.py +10 -2
  88. iatoolkit/views/prompt_api_view.py +1 -1
  89. iatoolkit/views/rag_api_view.py +216 -0
  90. iatoolkit/views/root_redirect_view.py +22 -0
  91. iatoolkit/views/signup_view.py +12 -4
  92. iatoolkit/views/static_page_view.py +27 -0
  93. iatoolkit/views/users_api_view.py +33 -0
  94. iatoolkit/views/verify_user_view.py +1 -1
  95. iatoolkit-1.4.2.dist-info/METADATA +268 -0
  96. iatoolkit-1.4.2.dist-info/RECORD +133 -0
  97. iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  98. iatoolkit/repositories/tasks_repo.py +0 -52
  99. iatoolkit/services/history_service.py +0 -37
  100. iatoolkit/services/search_service.py +0 -55
  101. iatoolkit/services/tasks_service.py +0 -188
  102. iatoolkit/templates/about.html +0 -13
  103. iatoolkit/templates/index.html +0 -145
  104. iatoolkit/templates/login_simulation.html +0 -45
  105. iatoolkit/views/external_login_view.py +0 -73
  106. iatoolkit/views/index_view.py +0 -14
  107. iatoolkit/views/login_simulation_view.py +0 -93
  108. iatoolkit/views/tasks_api_view.py +0 -72
  109. iatoolkit/views/tasks_review_api_view.py +0 -55
  110. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  111. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  112. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
  113. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
  114. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
@@ -3,12 +3,13 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
+ from iatoolkit.common.interfaces.database_provider import DatabaseProvider
6
7
  from iatoolkit.repositories.database_manager import DatabaseManager
7
- from iatoolkit.common.util import Utility
8
8
  from iatoolkit.services.i18n_service import I18nService
9
9
  from iatoolkit.common.exceptions import IAToolkitException
10
- from sqlalchemy import text
10
+ from iatoolkit.common.util import Utility
11
11
  from injector import inject, singleton
12
+ from typing import Callable
12
13
  import json
13
14
  import logging
14
15
 
@@ -27,61 +28,124 @@ class SqlService:
27
28
  self.util = util
28
29
  self.i18n_service = i18n_service
29
30
 
30
- # Cache for database connections
31
- self._db_connections: dict[str, DatabaseManager] = {}
31
+ # Cache for database providers. Key is tuple: (company_short_name, db_name)
32
+ # Value is the abstract interface DatabaseProvider
33
+ self._db_connections: dict[tuple[str, str], DatabaseProvider] = {}
34
+
35
+ # cache for database schemas. Key is tuple: (company_short_name, db_name)
36
+ self._db_schemas: dict[tuple[str, str], str] = {}
37
+
38
+ # Registry of factory functions.
39
+ # Format: {'connection_type': function(config_dict) -> DatabaseProvider}
40
+ self._provider_factories: dict[str, Callable[[dict], DatabaseProvider]] = {}
41
+
42
+ # Register the default 'direct' strategy (SQLAlchemy)
43
+ self.register_provider_factory('direct', self._create_direct_connection)
44
+
45
+ def register_provider_factory(self, connection_type: str, factory: Callable[[dict], DatabaseProvider]):
46
+ """
47
+ Allows plugins (Enterprise) to register new connection types.
48
+ """
49
+ self._provider_factories[connection_type] = factory
50
+
51
+ def _create_direct_connection(self, config: dict) -> DatabaseProvider:
52
+ """Default factory for standard SQLAlchemy connections."""
53
+ uri = config.get('db_uri') or config.get('DATABASE_URI')
54
+ schema = config.get('schema')
55
+ if not uri:
56
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR,
57
+ "Missing db_uri for direct connection")
58
+ return DatabaseManager(uri, schema=schema, register_pgvector=False)
32
59
 
33
- def register_database(self, db_name: str, db_uri: str):
60
+ def register_database(self, company_short_name: str, db_name: str, config: dict):
34
61
  """
35
- Creates and caches a DatabaseManager instance for a given database name and URI.
36
- If a database with the same name is already registered, it does nothing.
62
+ Creates and caches a DatabaseProvider instance based on the configuration.
37
63
  """
38
- if db_name in self._db_connections:
64
+ key = (company_short_name, db_name)
65
+
66
+ # Determine connection type (default to 'direct')
67
+ conn_type = config.get('connection_type', 'direct')
68
+ logging.info(f"Registering DB '{db_name}' ({conn_type}) for company '{company_short_name}'")
69
+
70
+ factory = self._provider_factories.get(conn_type)
71
+ if not factory:
72
+ logging.error(f"Unknown connection type '{conn_type}' for DB '{db_name}'. Skipping.")
39
73
  return
40
74
 
41
- logging.debug(f"Registering and creating connection for database: '{db_name}'")
75
+ try:
76
+ # Create the provider using the appropriate factory
77
+ provider_instance = factory(config)
78
+ self._db_connections[key] = provider_instance
79
+
80
+ # save the db_schema
81
+ self._db_schemas[key] = config.get('schema', 'public')
82
+ except Exception as e:
83
+ logging.error(f"Failed to register DB '{db_name}': {e}")
84
+ # We don't raise here to allow other DBs to load if one fails
42
85
 
43
- # create the database connection and save it on the cache
44
- db_manager = DatabaseManager(db_uri, register_pgvector=False)
45
- self._db_connections[db_name] = db_manager
86
+ def get_db_names(self, company_short_name: str) -> list[str]:
87
+ """
88
+ Returns list of logical database names available ONLY for the specified company.
89
+ """
90
+ return [db for (co, db) in self._db_connections.keys() if co == company_short_name]
46
91
 
47
- def get_database_manager(self, db_name: str) -> DatabaseManager:
92
+ def get_database_provider(self, company_short_name: str, db_name: str) -> DatabaseProvider:
48
93
  """
49
- Retrieves a registered DatabaseManager instance from the cache.
94
+ Retrieves a registered DatabaseProvider instance using the composite key.
95
+ Replaces the old 'get_database_manager'.
50
96
  """
97
+ key = (company_short_name, db_name)
51
98
  try:
52
- return self._db_connections[db_name]
99
+ return self._db_connections[key]
53
100
  except KeyError:
54
- logging.error(f"Attempted to access unregistered database: '{db_name}'")
101
+ logging.error(
102
+ f"Attempted to access unregistered database: '{db_name}' for company '{company_short_name}'"
103
+ )
55
104
  raise IAToolkitException(
56
105
  IAToolkitException.ErrorType.DATABASE_ERROR,
57
- f"Database '{db_name}' is not registered with the SqlService."
106
+ f"Database '{db_name}' is not registered for this company."
58
107
  )
59
108
 
60
- def exec_sql(self, database: str, query: str) -> str:
109
+ def exec_sql(self, company_short_name: str, **kwargs):
61
110
  """
62
- Executes a raw SQL statement against a registered database and returns the result as a JSON string.
111
+ Executes a raw SQL statement against a registered database provider.
112
+ Delegates the actual execution details to the provider implementation.
63
113
  """
114
+ database_name = kwargs.get('database_key')
115
+ query = kwargs.get('query')
116
+ format = kwargs.get('format', 'json')
117
+ commit = kwargs.get('commit')
118
+
119
+ if not database_name:
120
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR,
121
+ 'missing database_name in call to exec_sql')
122
+
64
123
  try:
65
- # 1. Get the database manager from the cache
66
- db_manager = self.get_database_manager(database)
124
+ # 1. Get the abstract provider (could be Direct or Bridge)
125
+ provider = self.get_database_provider(company_short_name, database_name)
126
+ db_schema = self._db_schemas[(company_short_name, database_name)]
67
127
 
68
- # 2. Execute the SQL statement
69
- result = db_manager.get_session().execute(text(query))
70
- cols = result.keys()
71
- rows_context = [dict(zip(cols, row)) for row in result.fetchall()]
128
+ # 2. Delegate execution
129
+ # The provider returns a clean List[Dict] or Dict result
130
+ result_data = provider.execute_query(query=query, commit=commit)
72
131
 
73
- # seialize the result
74
- sql_result_json = json.dumps(rows_context, default=self.util.serialize)
132
+ # 3. Handle Formatting (Service layer responsibility)
133
+ if format == 'dict':
134
+ return result_data
135
+
136
+ # Serialize the result
137
+ return json.dumps(result_data, default=self.util.serialize)
75
138
 
76
- return sql_result_json
77
139
  except IAToolkitException:
78
- # Re-raise exceptions from get_database_manager to preserve the specific error
79
140
  raise
80
141
  except Exception as e:
81
- # Attempt to rollback if a session was active
82
- db_manager = self._db_connections.get(database)
83
- if db_manager:
84
- db_manager.get_session().rollback()
142
+ # Attempt rollback if supported/needed
143
+ try:
144
+ provider = self.get_database_provider(company_short_name, database_name)
145
+ if provider:
146
+ provider.rollback()
147
+ except Exception:
148
+ pass
85
149
 
86
150
  error_message = str(e)
87
151
  if 'timed out' in str(e):
@@ -89,4 +153,22 @@ class SqlService:
89
153
 
90
154
  logging.error(f"Error executing SQL statement: {error_message}")
91
155
  raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR,
92
- error_message) from e
156
+ error_message) from e
157
+
158
+ def commit(self, company_short_name: str, database_name: str):
159
+ """
160
+ Commits the current transaction for a registered database provider.
161
+ """
162
+ provider = self.get_database_provider(company_short_name, database_name)
163
+ try:
164
+ provider.commit()
165
+ except Exception as e:
166
+ # Try rollback
167
+ try:
168
+ provider.rollback()
169
+ except:
170
+ pass
171
+ logging.error(f"Error while committing sql: '{str(e)}'")
172
+ raise IAToolkitException(
173
+ IAToolkitException.ErrorType.DATABASE_ERROR, str(e)
174
+ )
@@ -0,0 +1,246 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from injector import inject
7
+ from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
8
+ from iatoolkit.repositories.profile_repo import ProfileRepo
9
+ from iatoolkit.repositories.models import Company, Tool
10
+ from iatoolkit.common.exceptions import IAToolkitException
11
+ from iatoolkit.services.sql_service import SqlService
12
+ from iatoolkit.services.excel_service import ExcelService
13
+ from iatoolkit.services.mail_service import MailService
14
+
15
+
16
+ _SYSTEM_TOOLS = [
17
+ {
18
+ "function_name": "iat_generate_excel",
19
+ "description": "Generador de Excel."
20
+ "Genera un archivo Excel (.xlsx) a partir de una lista de diccionarios. "
21
+ "Cada diccionario representa una fila del archivo. "
22
+ "el archivo se guarda en directorio de descargas."
23
+ "retorna diccionario con filename, attachment_token (para enviar archivo por mail)"
24
+ "content_type y download_link",
25
+ "parameters": {
26
+ "type": "object",
27
+ "properties": {
28
+ "filename": {
29
+ "type": "string",
30
+ "description": "Nombre del archivo de salida (ejemplo: 'reporte.xlsx')",
31
+ "pattern": "^.+\\.xlsx?$"
32
+ },
33
+ "sheet_name": {
34
+ "type": "string",
35
+ "description": "Nombre de la hoja dentro del Excel",
36
+ "minLength": 1
37
+ },
38
+ "data": {
39
+ "type": "array",
40
+ "description": "Lista de diccionarios. Cada diccionario representa una fila.",
41
+ "minItems": 1,
42
+ "items": {
43
+ "type": "object",
44
+ "properties": {},
45
+ "additionalProperties": {
46
+ "anyOf": [
47
+ {"type": "string"},
48
+ {"type": "number"},
49
+ {"type": "boolean"},
50
+ {"type": "null"},
51
+ {
52
+ "type": "string",
53
+ "format": "date"
54
+ }
55
+ ]
56
+ }
57
+ }
58
+ }
59
+ },
60
+ "required": ["filename", "sheet_name", "data"]
61
+ }
62
+ },
63
+ {
64
+ 'function_name': "iat_send_email",
65
+ 'description': "iatoolkit mail system. "
66
+ "envia mails cuando un usuario lo solicita.",
67
+ 'parameters': {
68
+ "type": "object",
69
+ "properties": {
70
+ "recipient": {"type": "string", "description": "email del destinatario"},
71
+ "subject": {"type": "string", "description": "asunto del email"},
72
+ "body": {"type": "string", "description": "HTML del email"},
73
+ "attachments": {
74
+ "type": "array",
75
+ "description": "Lista de archivos adjuntos codificados en base64",
76
+ "items": {
77
+ "type": "object",
78
+ "properties": {
79
+ "filename": {
80
+ "type": "string",
81
+ "description": "Nombre del archivo con su extensión (ej. informe.pdf)"
82
+ },
83
+ "content": {
84
+ "type": "string",
85
+ "description": "Contenido del archivo en b64."
86
+ },
87
+ "attachment_token": {
88
+ "type": "string",
89
+ "description": "token para descargar el archivo."
90
+ }
91
+ },
92
+ "required": ["filename", "content", "attachment_token"],
93
+ "additionalProperties": False
94
+ }
95
+ }
96
+ },
97
+ "required": ["recipient", "subject", "body", "attachments"]
98
+ }
99
+ },
100
+ {
101
+ "function_name": "iat_sql_query",
102
+ "description": "Servicio SQL de IAToolkit: debes utilizar este servicio para todas las consultas SQL a bases de datos.",
103
+ "parameters": {
104
+ "type": "object",
105
+ "properties": {
106
+ "database_key": {
107
+ "type": "string",
108
+ "description": "IMPORTANT: nombre de la base de datos a consultar."
109
+ },
110
+ "query": {
111
+ "type": "string",
112
+ "description": "string con la consulta en sql"
113
+ },
114
+ },
115
+ "required": ["database_key", "query"]
116
+ }
117
+ }
118
+ ]
119
+
120
+
121
+ class ToolService:
122
+ @inject
123
+ def __init__(self,
124
+ llm_query_repo: LLMQueryRepo,
125
+ profile_repo: ProfileRepo,
126
+ sql_service: SqlService,
127
+ excel_service: ExcelService,
128
+ mail_service: MailService):
129
+ self.llm_query_repo = llm_query_repo
130
+ self.profile_repo = profile_repo
131
+ self.sql_service = sql_service
132
+ self.excel_service = excel_service
133
+ self.mail_service = mail_service
134
+
135
+ # execution mapper for system tools
136
+ self.system_handlers = {
137
+ "iat_generate_excel": self.excel_service.excel_generator,
138
+ "iat_send_email": self.mail_service.send_mail,
139
+ "iat_sql_query": self.sql_service.exec_sql
140
+ }
141
+
142
+ def register_system_tools(self):
143
+ """Creates or updates system functions in the database."""
144
+ try:
145
+ # delete all system tools
146
+ self.llm_query_repo.delete_system_tools()
147
+
148
+ # create new system tools
149
+ for function in _SYSTEM_TOOLS:
150
+ new_tool = Tool(
151
+ company_id=None,
152
+ system_function=True,
153
+ name=function['function_name'],
154
+ description=function['description'],
155
+ parameters=function['parameters']
156
+ )
157
+ self.llm_query_repo.create_or_update_tool(new_tool)
158
+
159
+ self.llm_query_repo.commit()
160
+ except Exception as e:
161
+ self.llm_query_repo.rollback()
162
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
163
+
164
+ def sync_company_tools(self, company_short_name: str, tools_config: list):
165
+ """
166
+ Synchronizes tools from YAML config to Database (Create/Update/Delete strategy).
167
+ """
168
+ if not tools_config:
169
+ return
170
+
171
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
172
+ if not company:
173
+ raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
174
+ f'Company {company_short_name} not found')
175
+
176
+ try:
177
+ # 1. Get existing tools map for later cleanup
178
+ existing_tools = {
179
+ f.name: f for f in self.llm_query_repo.get_company_tools(company)
180
+ }
181
+ defined_tool_names = set()
182
+
183
+ # 2. Sync (Create or Update) from Config
184
+ for tool_data in tools_config:
185
+ name = tool_data['function_name']
186
+ defined_tool_names.add(name)
187
+
188
+ # Construct the tool object with current config values
189
+ # We create a new transient object and let the repo merge it
190
+ tool_obj = Tool(
191
+ company_id=company.id,
192
+ name=name,
193
+ description=tool_data['description'],
194
+ parameters=tool_data['params'],
195
+ system_function=False
196
+ )
197
+
198
+ # Always call create_or_update. The repo handles checking for existence by name.
199
+ self.llm_query_repo.create_or_update_tool(tool_obj)
200
+
201
+ # 3. Cleanup: Delete tools present in DB but not in Config
202
+ for name, tool in existing_tools.items():
203
+ # Ensure we don't delete system functions or active tools accidentally if logic changes,
204
+ # though get_company_tools filters by company_id so system functions shouldn't be here usually.
205
+ if not tool.system_function and (name not in defined_tool_names):
206
+ self.llm_query_repo.delete_tool(tool)
207
+
208
+ self.llm_query_repo.commit()
209
+
210
+ except Exception as e:
211
+ self.llm_query_repo.rollback()
212
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
213
+
214
+
215
+ def get_tools_for_llm(self, company: Company) -> list[dict]:
216
+ """
217
+ Returns the list of tools (System + Company) formatted for the LLM (OpenAI Schema).
218
+ """
219
+ tools = []
220
+
221
+ # get all the tools for the company and system
222
+ company_tools = self.llm_query_repo.get_company_tools(company)
223
+
224
+ for function in company_tools:
225
+ # clone for no modify the SQLAlchemy session object
226
+ params = function.parameters.copy() if function.parameters else {}
227
+ params["additionalProperties"] = False
228
+
229
+ ai_tool = {
230
+ "type": "function",
231
+ "name": function.name,
232
+ "description": function.description,
233
+ "parameters": params,
234
+ "strict": True
235
+ }
236
+ if function.name == 'iat_sql_query':
237
+ params['properties']['database_key']['enum'] = self.sql_service.get_db_names(company.short_name)
238
+
239
+ tools.append(ai_tool)
240
+ return tools
241
+
242
+ def get_system_handler(self, function_name: str):
243
+ return self.system_handlers.get(function_name)
244
+
245
+ def is_system_tool(self, function_name: str) -> bool:
246
+ return function_name in self.system_handlers
@@ -8,7 +8,7 @@ from injector import inject
8
8
  from iatoolkit.repositories.profile_repo import ProfileRepo
9
9
  from iatoolkit.services.i18n_service import I18nService
10
10
  from iatoolkit.infra.google_chat_app import GoogleChatApp
11
- from iatoolkit.infra.mail_app import MailApp
11
+ from iatoolkit.services.mail_service import MailService
12
12
  import logging
13
13
 
14
14
 
@@ -18,11 +18,11 @@ class UserFeedbackService:
18
18
  profile_repo: ProfileRepo,
19
19
  i18n_service: I18nService,
20
20
  google_chat_app: GoogleChatApp,
21
- mail_app: MailApp):
21
+ mail_service: MailService):
22
22
  self.profile_repo = profile_repo
23
23
  self.i18n_service = i18n_service
24
24
  self.google_chat_app = google_chat_app
25
- self.mail_app = mail_app
25
+ self.mail_service = mail_service
26
26
 
27
27
  def _send_google_chat_notification(self, space_name: str, message_text: str):
28
28
  """Envía una notificación de feedback a un espacio de Google Chat."""
@@ -38,13 +38,21 @@ class UserFeedbackService:
38
38
  except Exception as e:
39
39
  logging.exception(f"error sending notification to Google Chat: {e}")
40
40
 
41
- def _send_email_notification(self, destination_email: str, company_name: str, message_text: str):
41
+ def _send_email_notification(self,
42
+ company_short_name: str,
43
+ destination_email: str,
44
+ company_name: str,
45
+ message_text: str):
42
46
  """Envía una notificación de feedback por correo electrónico."""
43
47
  try:
44
48
  subject = f"Nuevo Feedback de {company_name}"
45
49
  # Convertir el texto plano a un HTML simple para mantener los saltos de línea
46
50
  html_body = message_text.replace('\n', '<br>')
47
- self.mail_app.send_email(to=destination_email, subject=subject, body=html_body)
51
+ self.mail_service.send_mail(
52
+ company_short_name=company_short_name,
53
+ to=destination_email,
54
+ subject=subject,
55
+ body=html_body)
48
56
  except Exception as e:
49
57
  logging.exception(f"error sending email de feedback: {e}")
50
58
 
@@ -65,7 +73,11 @@ class UserFeedbackService:
65
73
  if channel == 'google_chat':
66
74
  self._send_google_chat_notification(space_name=destination, message_text=message_text)
67
75
  elif channel == 'email':
68
- self._send_email_notification(destination_email=destination, company_name=company.short_name, message_text=message_text)
76
+ self._send_email_notification(
77
+ company_short_name=company.short_name,
78
+ destination_email=destination,
79
+ company_name=company.short_name,
80
+ message_text=message_text)
69
81
  else:
70
82
  logging.warning(f"unknown feedback channel: '{channel}' for company {company.short_name}.")
71
83