iatoolkit 0.91.1__py3-none-any.whl → 1.7.0__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 (71) hide show
  1. iatoolkit/__init__.py +6 -4
  2. iatoolkit/base_company.py +0 -16
  3. iatoolkit/cli_commands.py +3 -14
  4. iatoolkit/common/exceptions.py +1 -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 +43 -0
  8. iatoolkit/common/model_registry.py +159 -0
  9. iatoolkit/common/routes.py +47 -5
  10. iatoolkit/common/util.py +32 -13
  11. iatoolkit/company_registry.py +5 -0
  12. iatoolkit/core.py +51 -20
  13. iatoolkit/infra/connectors/file_connector_factory.py +1 -0
  14. iatoolkit/infra/connectors/s3_connector.py +4 -2
  15. iatoolkit/infra/llm_providers/__init__.py +0 -0
  16. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  17. iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
  18. iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
  19. iatoolkit/infra/llm_proxy.py +235 -134
  20. iatoolkit/infra/llm_response.py +5 -0
  21. iatoolkit/locales/en.yaml +158 -2
  22. iatoolkit/locales/es.yaml +158 -0
  23. iatoolkit/repositories/database_manager.py +52 -47
  24. iatoolkit/repositories/document_repo.py +7 -0
  25. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  26. iatoolkit/repositories/llm_query_repo.py +2 -0
  27. iatoolkit/repositories/models.py +72 -79
  28. iatoolkit/repositories/profile_repo.py +59 -3
  29. iatoolkit/repositories/vs_repo.py +22 -24
  30. iatoolkit/services/company_context_service.py +126 -53
  31. iatoolkit/services/configuration_service.py +299 -73
  32. iatoolkit/services/dispatcher_service.py +21 -3
  33. iatoolkit/services/file_processor_service.py +0 -5
  34. iatoolkit/services/history_manager_service.py +43 -24
  35. iatoolkit/services/knowledge_base_service.py +425 -0
  36. iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
  37. iatoolkit/services/load_documents_service.py +26 -48
  38. iatoolkit/services/profile_service.py +32 -4
  39. iatoolkit/services/prompt_service.py +32 -30
  40. iatoolkit/services/query_service.py +51 -26
  41. iatoolkit/services/sql_service.py +122 -74
  42. iatoolkit/services/tool_service.py +26 -11
  43. iatoolkit/services/user_session_context_service.py +115 -63
  44. iatoolkit/static/js/chat_main.js +44 -4
  45. iatoolkit/static/js/chat_model_selector.js +227 -0
  46. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  47. iatoolkit/static/js/chat_reload_button.js +4 -1
  48. iatoolkit/static/styles/chat_iatoolkit.css +58 -2
  49. iatoolkit/static/styles/llm_output.css +34 -1
  50. iatoolkit/system_prompts/query_main.prompt +26 -2
  51. iatoolkit/templates/base.html +13 -0
  52. iatoolkit/templates/chat.html +45 -2
  53. iatoolkit/templates/onboarding_shell.html +0 -1
  54. iatoolkit/views/base_login_view.py +7 -2
  55. iatoolkit/views/chat_view.py +76 -0
  56. iatoolkit/views/configuration_api_view.py +163 -0
  57. iatoolkit/views/load_document_api_view.py +14 -10
  58. iatoolkit/views/login_view.py +8 -3
  59. iatoolkit/views/rag_api_view.py +216 -0
  60. iatoolkit/views/users_api_view.py +33 -0
  61. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/METADATA +4 -4
  62. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/RECORD +66 -58
  63. iatoolkit/repositories/tasks_repo.py +0 -52
  64. iatoolkit/services/search_service.py +0 -55
  65. iatoolkit/services/tasks_service.py +0 -188
  66. iatoolkit/views/tasks_api_view.py +0 -72
  67. iatoolkit/views/tasks_review_api_view.py +0 -55
  68. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/WHEEL +0 -0
  69. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE +0 -0
  70. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
  71. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/top_level.txt +0 -0
@@ -1,188 +0,0 @@
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.models import Task, TaskStatus
8
- from iatoolkit.services.query_service import QueryService
9
- from iatoolkit.repositories.tasks_repo import TaskRepo
10
- from iatoolkit.repositories.profile_repo import ProfileRepo
11
- from iatoolkit.infra.call_service import CallServiceClient
12
- from iatoolkit.common.exceptions import IAToolkitException
13
- from datetime import datetime
14
- from werkzeug.utils import secure_filename
15
-
16
-
17
- class TaskService:
18
- @inject
19
- def __init__(self,
20
- task_repo: TaskRepo,
21
- query_service: QueryService,
22
- profile_repo: ProfileRepo,
23
- call_service: CallServiceClient):
24
- self.task_repo = task_repo
25
- self.query_service = query_service
26
- self.profile_repo = profile_repo
27
- self.call_service = call_service
28
-
29
- def create_task(self,
30
- company_short_name: str,
31
- task_type_name: str,
32
- client_data: dict,
33
- company_task_id: int= 0,
34
- execute_at: datetime = None,
35
- files: list = []
36
- ) -> Task:
37
-
38
- # validate company
39
- company = self.profile_repo.get_company_by_short_name(company_short_name)
40
- if not company:
41
- raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
42
- f'No existe la empresa: {company_short_name}')
43
-
44
- # validate task_type
45
- task_type = self.task_repo.get_task_type(task_type_name)
46
- if not task_type:
47
- raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
48
- f'No existe el task_type: {task_type_name}')
49
-
50
- # process the task files
51
- task_files = self.get_task_files(files)
52
-
53
- # create Task object
54
- new_task = Task(
55
- company_id=company.id,
56
- task_type_id=task_type.id,
57
- company_task_id=company_task_id,
58
- client_data=client_data,
59
- execute_at=execute_at,
60
- files=task_files
61
- )
62
- new_task = self.task_repo.create_task(new_task)
63
- if execute_at and execute_at > datetime.now():
64
- self.execute_task(new_task)
65
-
66
- return new_task
67
-
68
- def review_task(self, task_id: int, review_user: str, approved: bool, comment: str):
69
- # get the task
70
- task = self.task_repo.get_task_by_id(task_id)
71
- if not task:
72
- raise IAToolkitException(IAToolkitException.ErrorType.TASK_NOT_FOUND,
73
- f'No existe la tarea: {task_id}')
74
-
75
- if task.status != TaskStatus.ejecutado:
76
- raise IAToolkitException(IAToolkitException.ErrorType.INVALID_STATE,
77
- f'La tarea debe estar en estado ejecutada: {task_id}')
78
-
79
- # update the task
80
- task.approved = approved
81
- task.status = TaskStatus.aprobada if approved else TaskStatus.rechazada
82
- task.review_user = review_user
83
- task.comment = comment
84
- task.review_date = datetime.now()
85
- self.task_repo.update_task(task)
86
- return task
87
-
88
- def execute_task(self, task: Task):
89
- # in this case do nothing
90
- if (task.status != TaskStatus.pendiente or
91
- (task.execute_at and task.execute_at > datetime.now())):
92
- return task
93
-
94
- # get the Task template prompt
95
- if not task.task_type.prompt_template:
96
- raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
97
- f'No existe el prompt_template para el task_type: {task.task_type.name}')
98
-
99
- template_dir = f'companies/{task.company.short_name}/prompts'
100
-
101
- # call the IA
102
- response = self.query_service.llm_query(
103
- task=task,
104
- user_identifier='task-monitor',
105
- company_short_name=task.company.short_name,
106
- prompt_name=task.task_type.name,
107
- client_data=task.client_data,
108
- files=task.files
109
- )
110
- if 'error' in response:
111
- raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR,
112
- response.get('error'))
113
-
114
- # update the Task with the response from llm_query
115
- task.llm_query_id = response.get('query_id', 0)
116
-
117
- # update task status
118
- if not response.get('valid_response'):
119
- task.status = TaskStatus.fallida
120
- else:
121
- task.status = TaskStatus.ejecutado
122
- self.task_repo.update_task(task)
123
-
124
- # call the callback url
125
- if task.callback_url:
126
- self.notify_callback(task, response)
127
-
128
- return task
129
-
130
- def notify_callback(self, task: Task, response: dict):
131
- response_data = {
132
- 'task_id': task.id,
133
- 'company_task_id': task.company_task_id,
134
- 'status': task.status.name,
135
- 'answer': response.get('answer', ''),
136
- 'additional_data': response.get('additional_data', {}),
137
- 'client_data': task.client_data,
138
- }
139
- try:
140
- response, status_code = self.call_service.post(task.callback_url, response_data)
141
- except Exception as e:
142
- raise IAToolkitException(
143
- IAToolkitException.ErrorType.REQUEST_ERROR,
144
- f"Error al notificar callback {task.callback_url}: {str(e)}"
145
- )
146
-
147
- def get_task_files(self, uploaded_files):
148
- files_info = []
149
-
150
- for file in uploaded_files:
151
- filename = secure_filename(file.filename)
152
-
153
- try:
154
- # the file is already in base64
155
- file_content = file.read().decode('utf-8')
156
- except Exception as e:
157
- raise IAToolkitException(
158
- IAToolkitException.ErrorType.FILE_IO_ERROR,
159
- f"Error al extraer el contenido del archivo {filename}: {str(e)}"
160
- )
161
-
162
- files_info.append({
163
- 'filename': filename,
164
- 'content': file_content, # file in base64
165
- 'type': file.content_type
166
- })
167
-
168
- return files_info
169
-
170
- def trigger_pending_tasks(self, company_short_name: str):
171
- n_tasks = 0
172
- try:
173
- company = self.profile_repo.get_company_by_short_name(company_short_name)
174
- pending_tasks = self.task_repo.get_pending_tasks(company.id)
175
- for task in pending_tasks:
176
- self.execute_task(task)
177
- n_tasks += 1
178
- except Exception as e:
179
- raise IAToolkitException(
180
- IAToolkitException.ErrorType.TASK_EXECUTION_ERROR,
181
- f"Error ejecutando tareas pendientes: {str(e)}"
182
- )
183
-
184
- return {'message': f'{n_tasks} tareas ejecutadas.'}
185
-
186
-
187
-
188
-
@@ -1,72 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- from flask.views import MethodView
7
- from flask import request, jsonify
8
- from iatoolkit.services.tasks_service import TaskService
9
- from iatoolkit.repositories.profile_repo import ProfileRepo
10
- from iatoolkit.services.auth_service import AuthService
11
- from injector import inject
12
- from datetime import datetime
13
- import logging
14
- from typing import Optional
15
-
16
-
17
- class TaskApiView(MethodView):
18
- @inject
19
- def __init__(self,
20
- auth_service: AuthService,
21
- task_service: TaskService,
22
- profile_repo: ProfileRepo):
23
- self.auth_service = auth_service
24
- self.task_service = task_service
25
- self.profile_repo = profile_repo
26
-
27
- def post(self):
28
- try:
29
- auth_result = self.auth_service.verify(anonymous=True)
30
- if not auth_result.get("success"):
31
- return jsonify(auth_result), auth_result.get("status_code")
32
-
33
- req_data = request.get_json()
34
- files = request.files.getlist('files')
35
-
36
- required_fields = ['company', 'task_type', 'client_data']
37
- for field in required_fields:
38
- if field not in req_data:
39
- return jsonify({"error": f"El campo {field} es requerido"}), 400
40
-
41
- company_short_name = req_data.get('company', '')
42
- task_type = req_data.get('task_type', '')
43
- client_data = req_data.get('client_data', {})
44
- company_task_id = req_data.get('company_task_id', 0)
45
- execute_at = req_data.get('execute_at', None)
46
-
47
- # validate date format is parameter is present
48
- if execute_at:
49
- try:
50
- # date in iso format
51
- execute_at = datetime.fromisoformat(execute_at)
52
- except ValueError:
53
- return jsonify({
54
- "error": "El formato de execute_at debe ser YYYY-MM-DD HH:MM:SS"
55
- }), 400
56
-
57
- new_task = self.task_service.create_task(
58
- company_short_name=company_short_name,
59
- task_type_name=task_type,
60
- client_data=client_data,
61
- company_task_id=company_task_id,
62
- execute_at=execute_at,
63
- files=files)
64
-
65
- return jsonify({
66
- "task_id": new_task.id,
67
- "status": new_task.status.name
68
- }), 201
69
-
70
- except Exception as e:
71
- logging.exception("Error al crear la tarea: %s", str(e))
72
- return jsonify({"error": str(e)}), 500
@@ -1,55 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- from flask.views import MethodView
7
- from flask import request, jsonify
8
- from iatoolkit.services.tasks_service import TaskService
9
- from iatoolkit.repositories.profile_repo import ProfileRepo
10
- from iatoolkit.services.auth_service import AuthService
11
- from injector import inject
12
- import logging
13
- from typing import Optional
14
-
15
-
16
- class TaskReviewApiView(MethodView):
17
- @inject
18
- def __init__(self,
19
- auth_service: AuthService,
20
- task_service: TaskService,
21
- profile_repo: ProfileRepo):
22
- self.auth_service = auth_service
23
- self.task_service = task_service
24
- self.profile_repo = profile_repo
25
-
26
- def post(self, task_id: int):
27
- auth_result = self.auth_service.verify(anonymous=True)
28
- if not auth_result.get("success"):
29
- return jsonify(auth_result), auth_result.get("status_code")
30
-
31
- try:
32
- req_data = request.get_json()
33
- required_fields = ['review_user', 'approved']
34
- for field in required_fields:
35
- if field not in req_data:
36
- return jsonify({"error": f"El campo {field} es requerido"}), 400
37
-
38
- review_user = req_data.get('review_user', '')
39
- approved = req_data.get('approved', False)
40
- comment = req_data.get('comment', '')
41
-
42
- new_task = self.task_service.review_task(
43
- task_id=task_id,
44
- review_user=review_user,
45
- approved=approved,
46
- comment=comment)
47
-
48
- return jsonify({
49
- "task_id": new_task.id,
50
- "status": new_task.status.name
51
- }), 200
52
-
53
- except Exception as e:
54
- logging.exception("Error al revisar la tarea: %s", str(e))
55
- return jsonify({"error": str(e)}), 500