iatoolkit 0.91.1__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.
- iatoolkit/__init__.py +6 -4
- iatoolkit/base_company.py +0 -16
- iatoolkit/cli_commands.py +3 -14
- iatoolkit/common/exceptions.py +1 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +38 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +42 -5
- iatoolkit/common/util.py +11 -12
- iatoolkit/company_registry.py +5 -0
- iatoolkit/core.py +51 -20
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
- iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
- iatoolkit/infra/llm_proxy.py +235 -134
- iatoolkit/infra/llm_response.py +5 -0
- iatoolkit/locales/en.yaml +124 -2
- iatoolkit/locales/es.yaml +122 -0
- iatoolkit/repositories/database_manager.py +44 -19
- iatoolkit/repositories/document_repo.py +7 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +2 -0
- iatoolkit/repositories/models.py +72 -79
- iatoolkit/repositories/profile_repo.py +59 -3
- iatoolkit/repositories/vs_repo.py +22 -24
- iatoolkit/services/company_context_service.py +88 -39
- iatoolkit/services/configuration_service.py +157 -68
- iatoolkit/services/dispatcher_service.py +21 -3
- iatoolkit/services/file_processor_service.py +0 -5
- iatoolkit/services/history_manager_service.py +43 -24
- iatoolkit/services/knowledge_base_service.py +412 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
- iatoolkit/services/load_documents_service.py +18 -47
- iatoolkit/services/profile_service.py +32 -4
- iatoolkit/services/prompt_service.py +32 -30
- iatoolkit/services/query_service.py +51 -26
- iatoolkit/services/sql_service.py +105 -74
- iatoolkit/services/tool_service.py +26 -11
- iatoolkit/services/user_session_context_service.py +115 -63
- iatoolkit/static/js/chat_main.js +44 -4
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/js/chat_reload_button.js +4 -1
- iatoolkit/static/styles/chat_iatoolkit.css +58 -2
- iatoolkit/static/styles/llm_output.css +34 -1
- iatoolkit/system_prompts/query_main.prompt +26 -2
- iatoolkit/templates/base.html +13 -0
- iatoolkit/templates/chat.html +44 -2
- iatoolkit/templates/onboarding_shell.html +0 -1
- iatoolkit/views/base_login_view.py +7 -2
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/load_document_api_view.py +14 -10
- iatoolkit/views/login_view.py +8 -3
- iatoolkit/views/rag_api_view.py +216 -0
- iatoolkit/views/users_api_view.py +33 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/METADATA +4 -4
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/RECORD +64 -56
- iatoolkit/repositories/tasks_repo.py +0 -52
- iatoolkit/services/search_service.py +0 -55
- iatoolkit/services/tasks_service.py +0 -188
- iatoolkit/views/tasks_api_view.py +0 -72
- iatoolkit/views/tasks_review_api_view.py +0 -55
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
from iatoolkit.repositories.vs_repo import VSRepo
|
|
7
|
-
from iatoolkit.repositories.document_repo import DocumentRepo
|
|
8
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
|
-
from iatoolkit.repositories.models import Company
|
|
10
|
-
from injector import inject
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SearchService:
|
|
14
|
-
@inject
|
|
15
|
-
def __init__(self,
|
|
16
|
-
profile_repo: ProfileRepo,
|
|
17
|
-
doc_repo: DocumentRepo,
|
|
18
|
-
vs_repo: VSRepo):
|
|
19
|
-
self.profile_repo = profile_repo
|
|
20
|
-
self.vs_repo = vs_repo
|
|
21
|
-
self.doc_repo = doc_repo
|
|
22
|
-
|
|
23
|
-
def search(self, company_short_name: str, query: str, metadata_filter: dict = None) -> str:
|
|
24
|
-
"""
|
|
25
|
-
Performs a semantic search for a given query within a company's documents.
|
|
26
|
-
|
|
27
|
-
This method queries the vector store for relevant documents based on the
|
|
28
|
-
provided query text. It then constructs a formatted string containing the
|
|
29
|
-
content of the retrieved documents, which can be used as context for an LLM.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
company_short_name: The company to search within.
|
|
33
|
-
query: The text query to search for.
|
|
34
|
-
metadata_filter: An optional dictionary to filter documents by their metadata.
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
A string containing the concatenated content of the found documents,
|
|
38
|
-
formatted to be used as a context.
|
|
39
|
-
"""
|
|
40
|
-
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
41
|
-
if not company:
|
|
42
|
-
return f"error: company {company_short_name} not found"
|
|
43
|
-
|
|
44
|
-
document_list = self.vs_repo.query(company_short_name=company_short_name,
|
|
45
|
-
query_text=query,
|
|
46
|
-
metadata_filter=metadata_filter)
|
|
47
|
-
|
|
48
|
-
search_context = ''
|
|
49
|
-
for doc in document_list:
|
|
50
|
-
search_context += f'documento "{doc.filename}"'
|
|
51
|
-
if doc.meta and 'document_type' in doc.meta:
|
|
52
|
-
search_context += f' tipo: {doc.meta.get('document_type', '')}'
|
|
53
|
-
search_context += f': {doc.content}\n'
|
|
54
|
-
|
|
55
|
-
return search_context
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|