iatoolkit 1.9.0__py3-none-any.whl → 1.15.3__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 +1 -1
- iatoolkit/common/routes.py +1 -1
- iatoolkit/common/util.py +8 -123
- iatoolkit/core.py +1 -0
- iatoolkit/infra/connectors/file_connector.py +10 -2
- iatoolkit/infra/connectors/google_drive_connector.py +3 -0
- iatoolkit/infra/connectors/local_file_connector.py +3 -0
- iatoolkit/infra/connectors/s3_connector.py +24 -1
- iatoolkit/infra/llm_providers/deepseek_adapter.py +17 -1
- iatoolkit/infra/llm_providers/gemini_adapter.py +117 -18
- iatoolkit/infra/llm_providers/openai_adapter.py +175 -18
- iatoolkit/infra/llm_response.py +13 -0
- iatoolkit/locales/en.yaml +47 -2
- iatoolkit/locales/es.yaml +45 -1
- iatoolkit/repositories/llm_query_repo.py +44 -33
- iatoolkit/services/company_context_service.py +294 -133
- iatoolkit/services/dispatcher_service.py +1 -1
- iatoolkit/services/knowledge_base_service.py +26 -4
- iatoolkit/services/llm_client_service.py +58 -2
- iatoolkit/services/prompt_service.py +236 -330
- iatoolkit/services/query_service.py +37 -18
- iatoolkit/services/storage_service.py +92 -0
- iatoolkit/static/js/chat_filepond.js +188 -63
- iatoolkit/static/js/chat_main.js +105 -52
- iatoolkit/static/styles/chat_iatoolkit.css +96 -0
- iatoolkit/system_prompts/query_main.prompt +24 -41
- iatoolkit/templates/chat.html +15 -6
- iatoolkit/views/base_login_view.py +1 -1
- iatoolkit/views/categories_api_view.py +43 -3
- iatoolkit/views/chat_view.py +1 -1
- iatoolkit/views/login_view.py +1 -1
- iatoolkit/views/prompt_api_view.py +1 -1
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/METADATA +1 -1
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/RECORD +38 -37
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/WHEEL +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/top_level.txt +0 -0
|
@@ -21,6 +21,7 @@ import re
|
|
|
21
21
|
import tiktoken
|
|
22
22
|
from typing import Dict, Optional, List
|
|
23
23
|
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
24
|
+
from iatoolkit.services.storage_service import StorageService
|
|
24
25
|
|
|
25
26
|
CONTEXT_ERROR_MESSAGE = 'Tu consulta supera el límite de contexto, utiliza el boton de recarga de contexto.'
|
|
26
27
|
|
|
@@ -33,11 +34,13 @@ class llmClient:
|
|
|
33
34
|
llmquery_repo: LLMQueryRepo,
|
|
34
35
|
llm_proxy: LLMProxy,
|
|
35
36
|
model_registry: ModelRegistry,
|
|
37
|
+
storage_service: StorageService,
|
|
36
38
|
util: Utility
|
|
37
39
|
):
|
|
38
40
|
self.llmquery_repo = llmquery_repo
|
|
39
41
|
self.llm_proxy = llm_proxy
|
|
40
42
|
self.model_registry = model_registry
|
|
43
|
+
self.storage_service = storage_service
|
|
41
44
|
self.util = util
|
|
42
45
|
self._dispatcher = None # Cache for the lazy-loaded dispatcher
|
|
43
46
|
|
|
@@ -69,8 +72,10 @@ class llmClient:
|
|
|
69
72
|
text: dict,
|
|
70
73
|
model: str,
|
|
71
74
|
context_history: Optional[List[Dict]] = None,
|
|
75
|
+
images: list = None,
|
|
72
76
|
) -> dict:
|
|
73
77
|
|
|
78
|
+
images = images or []
|
|
74
79
|
f_calls = [] # keep track of the function calls executed by the LLM
|
|
75
80
|
f_call_time = 0
|
|
76
81
|
response = None
|
|
@@ -84,7 +89,7 @@ class llmClient:
|
|
|
84
89
|
|
|
85
90
|
try:
|
|
86
91
|
start_time = time.time()
|
|
87
|
-
logging.info(f"calling llm model '{model}' with {self.count_tokens(context, context_history)} tokens...")
|
|
92
|
+
logging.info(f"calling llm model '{model}' with {self.count_tokens(context, context_history)} tokens...and {len(images)} images...")
|
|
88
93
|
|
|
89
94
|
# this is the first call to the LLM on the iteration
|
|
90
95
|
try:
|
|
@@ -102,6 +107,7 @@ class llmClient:
|
|
|
102
107
|
tools=tools,
|
|
103
108
|
text=text_payload,
|
|
104
109
|
reasoning=reasoning,
|
|
110
|
+
images=images,
|
|
105
111
|
)
|
|
106
112
|
stats = self.get_stats(response)
|
|
107
113
|
|
|
@@ -163,7 +169,7 @@ class llmClient:
|
|
|
163
169
|
error_message = f"Dispatch error en {function_name} con args {args} -******- {str(e)}"
|
|
164
170
|
raise IAToolkitException(IAToolkitException.ErrorType.CALL_ERROR, error_message)
|
|
165
171
|
|
|
166
|
-
# add
|
|
172
|
+
# add the return value into the list of messages
|
|
167
173
|
input_messages.append({
|
|
168
174
|
"type": "function_call_output",
|
|
169
175
|
"call_id": tool_call.call_id,
|
|
@@ -198,9 +204,14 @@ class llmClient:
|
|
|
198
204
|
tool_choice=tool_choice_value,
|
|
199
205
|
tools=tools,
|
|
200
206
|
text=text_payload,
|
|
207
|
+
images=images,
|
|
201
208
|
)
|
|
202
209
|
stats_fcall = self.add_stats(stats_fcall, self.get_stats(response))
|
|
203
210
|
|
|
211
|
+
# --- IMAGE PROCESSING ---
|
|
212
|
+
# before save or respond, upload the images to S3 and clean content_parts
|
|
213
|
+
self._process_generated_images(response, company.short_name)
|
|
214
|
+
|
|
204
215
|
# save the statistices
|
|
205
216
|
stats['response_time']=int(time.time() - start_time)
|
|
206
217
|
stats['sql_retry_count'] = sql_retry_count
|
|
@@ -239,6 +250,7 @@ class llmClient:
|
|
|
239
250
|
'query_id': query.id,
|
|
240
251
|
'model': model,
|
|
241
252
|
'reasoning_content': final_reasoning,
|
|
253
|
+
'content_parts': response.content_parts
|
|
242
254
|
}
|
|
243
255
|
except SQLAlchemyError as db_error:
|
|
244
256
|
# rollback
|
|
@@ -295,6 +307,50 @@ class llmClient:
|
|
|
295
307
|
|
|
296
308
|
return response.id
|
|
297
309
|
|
|
310
|
+
def _process_generated_images(self, response, company_short_name: str):
|
|
311
|
+
"""
|
|
312
|
+
Traverse content_parts, detect images in Base64, upload to S3 and update content_parts.
|
|
313
|
+
"""
|
|
314
|
+
if not response.content_parts:
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
for part in response.content_parts:
|
|
318
|
+
if part.get('type') == 'image':
|
|
319
|
+
source = part.get('source', {})
|
|
320
|
+
if source.get('type') in ['base64', 'url']:
|
|
321
|
+
try:
|
|
322
|
+
if source.get('type') == 'url':
|
|
323
|
+
url = source.get('url')
|
|
324
|
+
storage_key = None
|
|
325
|
+
else:
|
|
326
|
+
# upload image to S3
|
|
327
|
+
result = self.storage_service.store_generated_image(
|
|
328
|
+
company_short_name,
|
|
329
|
+
source.get('data'),
|
|
330
|
+
source.get('media_type', 'image/png')
|
|
331
|
+
)
|
|
332
|
+
url = result['url']
|
|
333
|
+
storage_key = result['storage_key']
|
|
334
|
+
|
|
335
|
+
# Update content_part: Now it's a remote reference, not base64 anymore.
|
|
336
|
+
# We keep 'url' for the frontend to display it itself, and storage_key for internal reference.
|
|
337
|
+
part['source'] = {
|
|
338
|
+
'type': 'url',
|
|
339
|
+
'url': url,
|
|
340
|
+
'storage_key': storage_key,
|
|
341
|
+
'media_type': source.get('media_type')
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# clean data
|
|
345
|
+
logging.info(f"Imagen procesada y subida: {url}")
|
|
346
|
+
|
|
347
|
+
except Exception as e:
|
|
348
|
+
logging.error(f"Fallo al subir imagen generada: {e}")
|
|
349
|
+
|
|
350
|
+
# Fallback: keep the base64 and signal the error
|
|
351
|
+
part['error'] = "Failed to upload image"
|
|
352
|
+
|
|
353
|
+
|
|
298
354
|
def decode_response(self, response) -> dict:
|
|
299
355
|
message = response.output_text
|
|
300
356
|
decoded_response = {
|