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.
- iatoolkit/__init__.py +19 -7
- iatoolkit/base_company.py +1 -71
- iatoolkit/cli_commands.py +9 -21
- iatoolkit/common/exceptions.py +2 -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 +53 -32
- iatoolkit/common/util.py +17 -12
- iatoolkit/company_registry.py +55 -14
- iatoolkit/{iatoolkit.py → core.py} +102 -72
- iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
- 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 +134 -4
- iatoolkit/locales/es.yaml +293 -162
- iatoolkit/repositories/database_manager.py +92 -22
- iatoolkit/repositories/document_repo.py +7 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +36 -22
- iatoolkit/repositories/models.py +86 -95
- iatoolkit/repositories/profile_repo.py +64 -13
- iatoolkit/repositories/vs_repo.py +31 -28
- iatoolkit/services/auth_service.py +1 -1
- iatoolkit/services/branding_service.py +1 -1
- iatoolkit/services/company_context_service.py +96 -39
- iatoolkit/services/configuration_service.py +329 -67
- iatoolkit/services/dispatcher_service.py +51 -227
- iatoolkit/services/document_service.py +10 -1
- iatoolkit/services/embedding_service.py +9 -6
- iatoolkit/services/excel_service.py +50 -2
- iatoolkit/services/file_processor_service.py +0 -5
- iatoolkit/services/history_manager_service.py +208 -0
- iatoolkit/services/jwt_service.py +1 -1
- iatoolkit/services/knowledge_base_service.py +412 -0
- iatoolkit/services/language_service.py +8 -2
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
- iatoolkit/services/load_documents_service.py +18 -47
- iatoolkit/services/mail_service.py +171 -25
- iatoolkit/services/profile_service.py +69 -36
- iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
- iatoolkit/services/query_service.py +229 -203
- iatoolkit/services/sql_service.py +116 -34
- iatoolkit/services/tool_service.py +246 -0
- iatoolkit/services/user_feedback_service.py +18 -6
- iatoolkit/services/user_session_context_service.py +121 -51
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +0 -0
- iatoolkit/static/js/chat_feedback_button.js +1 -1
- iatoolkit/static/js/chat_help_content.js +4 -4
- iatoolkit/static/js/chat_main.js +61 -9
- 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 +59 -3
- iatoolkit/static/styles/chat_public.css +28 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +223 -7
- iatoolkit/static/styles/llm_output.css +34 -1
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +28 -3
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +30 -5
- iatoolkit/templates/_login_widget.html +3 -3
- iatoolkit/templates/base.html +13 -0
- iatoolkit/templates/chat.html +45 -3
- iatoolkit/templates/forgot_password.html +3 -2
- iatoolkit/templates/onboarding_shell.html +1 -2
- iatoolkit/templates/signup.html +3 -0
- iatoolkit/views/base_login_view.py +8 -3
- iatoolkit/views/change_password_view.py +1 -1
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/forgot_password_view.py +9 -4
- iatoolkit/views/history_api_view.py +3 -3
- iatoolkit/views/home_view.py +4 -2
- iatoolkit/views/init_context_api_view.py +1 -1
- iatoolkit/views/llmquery_api_view.py +4 -3
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
- iatoolkit/views/login_view.py +25 -8
- iatoolkit/views/logout_api_view.py +10 -2
- iatoolkit/views/prompt_api_view.py +1 -1
- iatoolkit/views/rag_api_view.py +216 -0
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +12 -4
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/users_api_view.py +33 -0
- iatoolkit/views/verify_user_view.py +1 -1
- iatoolkit-1.4.2.dist-info/METADATA +268 -0
- iatoolkit-1.4.2.dist-info/RECORD +133 -0
- iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- iatoolkit/repositories/tasks_repo.py +0 -52
- iatoolkit/services/history_service.py +0 -37
- iatoolkit/services/search_service.py +0 -55
- iatoolkit/services/tasks_service.py +0 -188
- iatoolkit/templates/about.html +0 -13
- iatoolkit/templates/index.html +0 -145
- iatoolkit/templates/login_simulation.html +0 -45
- iatoolkit/views/external_login_view.py +0 -73
- iatoolkit/views/index_view.py +0 -14
- iatoolkit/views/login_simulation_view.py +0 -93
- iatoolkit/views/tasks_api_view.py +0 -72
- iatoolkit/views/tasks_review_api_view.py +0 -55
- iatoolkit-0.71.4.dist-info/METADATA +0 -276
- iatoolkit-0.71.4.dist-info/RECORD +0 -122
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
iatoolkit/infra/llm_proxy.py
CHANGED
|
@@ -3,165 +3,266 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from abc import ABC, abstractmethod
|
|
8
|
-
from iatoolkit.common.util import Utility
|
|
9
|
-
from iatoolkit.infra.llm_response import LLMResponse
|
|
6
|
+
|
|
10
7
|
from iatoolkit.services.configuration_service import ConfigurationService
|
|
11
|
-
from iatoolkit.infra.openai_adapter import OpenAIAdapter
|
|
12
|
-
from iatoolkit.infra.gemini_adapter import GeminiAdapter
|
|
8
|
+
from iatoolkit.infra.llm_providers.openai_adapter import OpenAIAdapter
|
|
9
|
+
from iatoolkit.infra.llm_providers.gemini_adapter import GeminiAdapter
|
|
10
|
+
from iatoolkit.infra.llm_providers.deepseek_adapter import DeepseekAdapter
|
|
11
|
+
# from iatoolkit.infra.llm_providers.anthropic_adapter import AnthropicAdapter
|
|
13
12
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
14
|
-
from iatoolkit.
|
|
15
|
-
from
|
|
16
|
-
|
|
13
|
+
from iatoolkit.common.util import Utility
|
|
14
|
+
from iatoolkit.infra.llm_response import LLMResponse
|
|
15
|
+
from iatoolkit.common.model_registry import ModelRegistry
|
|
16
|
+
|
|
17
|
+
from openai import OpenAI # For OpenAI and xAI (OpenAI-compatible)
|
|
18
|
+
# from anthropic import Anthropic # For Claude (Anthropic)
|
|
19
|
+
|
|
20
|
+
from typing import Dict, List, Any, Tuple
|
|
17
21
|
import os
|
|
18
22
|
import threading
|
|
19
|
-
from enum import Enum
|
|
20
23
|
from injector import inject
|
|
21
24
|
|
|
22
25
|
|
|
23
|
-
class LLMProvider(Enum):
|
|
24
|
-
OPENAI = "openai"
|
|
25
|
-
GEMINI = "gemini"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class LLMAdapter(ABC):
|
|
29
|
-
"""common interface for LLM adapters"""
|
|
30
|
-
|
|
31
|
-
@abstractmethod
|
|
32
|
-
def create_response(self, *args, **kwargs) -> LLMResponse:
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
|
-
|
|
36
26
|
class LLMProxy:
|
|
37
27
|
"""
|
|
38
|
-
Proxy
|
|
39
|
-
de los clientes de los proveedores de LLM.
|
|
28
|
+
Proxy for routing calls to the correct LLM adapter and managing the creation of LLM clients.
|
|
40
29
|
"""
|
|
41
|
-
|
|
30
|
+
|
|
31
|
+
# Class-level cache for low-level clients (per provider + API key)
|
|
32
|
+
_clients_cache: Dict[Tuple[str, str], Any] = {}
|
|
42
33
|
_clients_cache_lock = threading.Lock()
|
|
43
34
|
|
|
35
|
+
# Provider identifiers
|
|
36
|
+
PROVIDER_OPENAI = "openai"
|
|
37
|
+
PROVIDER_GEMINI = "gemini"
|
|
38
|
+
PROVIDER_DEEPSEEK = "deepseek"
|
|
39
|
+
PROVIDER_XAI = "xai"
|
|
40
|
+
PROVIDER_ANTHROPIC = "anthropic"
|
|
41
|
+
|
|
44
42
|
@inject
|
|
45
|
-
def __init__(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
util: Utility,
|
|
46
|
+
configuration_service: ConfigurationService,
|
|
47
|
+
model_registry: ModelRegistry,
|
|
48
|
+
):
|
|
49
49
|
"""
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
Init a new instance of the proxy. It can be a base factory or a working instance with configured clients.
|
|
51
|
+
Pre-built clients can be injected for tests or special environments.
|
|
52
52
|
"""
|
|
53
53
|
self.util = util
|
|
54
54
|
self.configuration_service = configuration_service
|
|
55
|
-
self.
|
|
56
|
-
|
|
55
|
+
self.model_registry = model_registry
|
|
56
|
+
|
|
57
|
+
# adapter cache por provider
|
|
58
|
+
self.adapters: Dict[str, Any] = {}
|
|
59
|
+
|
|
60
|
+
# -------------------------------------------------------------------------
|
|
61
|
+
# Public API
|
|
62
|
+
# -------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def create_response(self, company_short_name: str, model: str, input: List[Dict], **kwargs) -> LLMResponse:
|
|
65
|
+
"""
|
|
66
|
+
Route the call to the correct adapter based on the model name.
|
|
67
|
+
This method is the single entry point used by the rest of the application.
|
|
68
|
+
"""
|
|
69
|
+
if not company_short_name:
|
|
70
|
+
raise IAToolkitException(
|
|
71
|
+
IAToolkitException.ErrorType.API_KEY,
|
|
72
|
+
"company_short_name is required in kwargs to resolve LLM credentials."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Determine the provider based on the model name
|
|
76
|
+
provider = self._resolve_provider_from_model(model)
|
|
77
|
+
|
|
78
|
+
adapter = self._get_or_create_adapter(
|
|
79
|
+
provider=provider,
|
|
80
|
+
company_short_name=company_short_name,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Delegate to the adapter (OpenAI, Gemini, DeepSeek, xAI, Anthropic, etc.)
|
|
84
|
+
return adapter.create_response(model=model, input=input, **kwargs)
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------------
|
|
87
|
+
# Provider resolution
|
|
88
|
+
# -------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def _resolve_provider_from_model(self, model: str) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Determine which provider must be used for a given model name.
|
|
93
|
+
This uses Utility helper methods, so you can keep all naming logic in one place.
|
|
94
|
+
"""
|
|
95
|
+
provider_key = self.model_registry.get_provider(model)
|
|
96
|
+
|
|
97
|
+
if provider_key == "openai":
|
|
98
|
+
return self.PROVIDER_OPENAI
|
|
99
|
+
if provider_key == "gemini":
|
|
100
|
+
return self.PROVIDER_GEMINI
|
|
101
|
+
if provider_key == "deepseek":
|
|
102
|
+
return self.PROVIDER_DEEPSEEK
|
|
103
|
+
if provider_key == "xai":
|
|
104
|
+
return self.PROVIDER_XAI
|
|
105
|
+
if provider_key == "anthropic":
|
|
106
|
+
return self.PROVIDER_ANTHROPIC
|
|
107
|
+
|
|
108
|
+
raise IAToolkitException(
|
|
109
|
+
IAToolkitException.ErrorType.MODEL,
|
|
110
|
+
f"Unknown or unsupported model: {model}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# -------------------------------------------------------------------------
|
|
114
|
+
# Adapter management
|
|
115
|
+
# -------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
def _get_or_create_adapter(self, provider: str, company_short_name: str) -> Any:
|
|
118
|
+
"""
|
|
119
|
+
Return an adapter instance for the given provider.
|
|
120
|
+
If none exists yet, create it using a cached or new low-level client.
|
|
121
|
+
"""
|
|
122
|
+
# If already created, just return it
|
|
123
|
+
if provider in self.adapters and self.adapters[provider] is not None:
|
|
124
|
+
return self.adapters[provider]
|
|
125
|
+
|
|
126
|
+
# Otherwise, create a low-level client from configuration
|
|
127
|
+
api_key = self._get_api_key_from_config(company_short_name, provider)
|
|
128
|
+
client = self._get_or_create_client(provider, api_key)
|
|
129
|
+
|
|
130
|
+
# Wrap client with the correct adapter
|
|
131
|
+
if provider == self.PROVIDER_OPENAI:
|
|
132
|
+
adapter = OpenAIAdapter(client)
|
|
133
|
+
elif provider == self.PROVIDER_GEMINI:
|
|
134
|
+
adapter = GeminiAdapter(client)
|
|
135
|
+
elif provider == self.PROVIDER_DEEPSEEK:
|
|
136
|
+
adapter = DeepseekAdapter(client)
|
|
137
|
+
else:
|
|
138
|
+
raise IAToolkitException(
|
|
139
|
+
IAToolkitException.ErrorType.MODEL,
|
|
140
|
+
f"Provider not supported in _get_or_create_adapter: {provider}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
'''
|
|
144
|
+
elif provider == self.PROVIDER_XAI:
|
|
145
|
+
adapter = XAIAdapter(client)
|
|
146
|
+
elif provider == self.PROVIDER_ANTHROPIC:
|
|
147
|
+
adapter = AnthropicAdapter(client)
|
|
148
|
+
'''
|
|
149
|
+
self.adapters[provider] = adapter
|
|
150
|
+
return adapter
|
|
151
|
+
|
|
152
|
+
# -------------------------------------------------------------------------
|
|
153
|
+
# Client cache
|
|
154
|
+
# -------------------------------------------------------------------------
|
|
57
155
|
|
|
58
|
-
def
|
|
156
|
+
def _get_or_create_client(self, provider: str, api_key: str) -> Any:
|
|
59
157
|
"""
|
|
60
|
-
|
|
158
|
+
Return a low-level client for the given provider and API key.
|
|
159
|
+
Uses a class-level cache to avoid recreating clients.
|
|
61
160
|
"""
|
|
62
|
-
|
|
63
|
-
openai_client = self._get_llm_connection(company, LLMProvider.OPENAI)
|
|
64
|
-
except IAToolkitException:
|
|
65
|
-
openai_client = None
|
|
161
|
+
cache_key = (provider, api_key or "")
|
|
66
162
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
gemini_client = None
|
|
163
|
+
with self._clients_cache_lock:
|
|
164
|
+
if cache_key in self._clients_cache:
|
|
165
|
+
return self._clients_cache[cache_key]
|
|
71
166
|
|
|
72
|
-
|
|
167
|
+
client = self._create_client_for_provider(provider, api_key)
|
|
168
|
+
self._clients_cache[cache_key] = client
|
|
169
|
+
return client
|
|
170
|
+
|
|
171
|
+
def _create_client_for_provider(self, provider: str, api_key: str) -> Any:
|
|
172
|
+
"""
|
|
173
|
+
Actually create the low-level client for a provider.
|
|
174
|
+
This is the only place where provider-specific client construction lives.
|
|
175
|
+
"""
|
|
176
|
+
if provider == self.PROVIDER_OPENAI:
|
|
177
|
+
# Standard OpenAI client for GPT models
|
|
178
|
+
return OpenAI(api_key=api_key)
|
|
179
|
+
|
|
180
|
+
if provider == self.PROVIDER_XAI:
|
|
181
|
+
# xAI Grok is OpenAI-compatible; we can use the OpenAI client with a different base_url.
|
|
182
|
+
return OpenAI(
|
|
183
|
+
api_key=api_key,
|
|
184
|
+
base_url="https://api.x.ai/v1",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if provider == self.PROVIDER_DEEPSEEK:
|
|
188
|
+
# Example: if you use the official deepseek client or OpenAI-compatible wrapper
|
|
189
|
+
# return DeepSeekAPI(api_key=api_key)
|
|
190
|
+
|
|
191
|
+
# We use OpenAI client with a DeepSeek base_url:
|
|
192
|
+
return OpenAI(
|
|
193
|
+
api_key=api_key,
|
|
194
|
+
base_url="https://api.deepseek.com",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if provider == self.PROVIDER_GEMINI:
|
|
198
|
+
# Example placeholder: you may already have a Gemini client factory elsewhere.
|
|
199
|
+
# Here you could create and configure the Gemini client (e.g. google.generativeai).
|
|
200
|
+
#
|
|
201
|
+
import google.generativeai as genai
|
|
202
|
+
|
|
203
|
+
genai.configure(api_key=api_key)
|
|
204
|
+
return genai
|
|
205
|
+
if provider == self.PROVIDER_ANTHROPIC:
|
|
206
|
+
# Example using Anthropic official client:
|
|
207
|
+
#
|
|
208
|
+
# from anthropic import Anthropic
|
|
209
|
+
# return Anthropic(api_key=api_key)
|
|
73
210
|
raise IAToolkitException(
|
|
74
211
|
IAToolkitException.ErrorType.API_KEY,
|
|
75
|
-
|
|
212
|
+
"Anthropic client creation must be implemented in _create_client_for_provider."
|
|
76
213
|
)
|
|
77
214
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
openai_client=openai_client,
|
|
83
|
-
gemini_client=gemini_client)
|
|
84
|
-
|
|
85
|
-
def create_response(self, model: str, input: List[Dict], **kwargs) -> LLMResponse:
|
|
86
|
-
"""Enruta la llamada al adaptador correcto basado en el modelo."""
|
|
87
|
-
# Se asume que esta instancia ya tiene los clientes configurados por `create_for_company`
|
|
88
|
-
if self.util.is_openai_model(model):
|
|
89
|
-
if not self.openai_adapter:
|
|
90
|
-
raise IAToolkitException(IAToolkitException.ErrorType.API_KEY,
|
|
91
|
-
f"No se configuró cliente OpenAI, pero se solicitó modelo OpenAI: {model}")
|
|
92
|
-
return self.openai_adapter.create_response(model=model, input=input, **kwargs)
|
|
93
|
-
elif self.util.is_gemini_model(model):
|
|
94
|
-
if not self.gemini_adapter:
|
|
95
|
-
raise IAToolkitException(IAToolkitException.ErrorType.API_KEY,
|
|
96
|
-
f"No se configuró cliente Gemini, pero se solicitó modelo Gemini: {model}")
|
|
97
|
-
return self.gemini_adapter.create_response(model=model, input=input, **kwargs)
|
|
98
|
-
else:
|
|
99
|
-
raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, f"Modelo no soportado: {model}")
|
|
100
|
-
|
|
101
|
-
def _get_llm_connection(self, company: Company, provider: LLMProvider) -> Any:
|
|
102
|
-
"""Obtiene una conexión de cliente para un proveedor, usando un caché para reutilizarla."""
|
|
103
|
-
cache_key = f"{company.short_name}_{provider.value}"
|
|
104
|
-
client = LLMProxy._clients_cache.get(cache_key)
|
|
105
|
-
|
|
106
|
-
if not client:
|
|
107
|
-
with LLMProxy._clients_cache_lock:
|
|
108
|
-
client = LLMProxy._clients_cache.get(cache_key)
|
|
109
|
-
if not client:
|
|
110
|
-
if provider == LLMProvider.OPENAI:
|
|
111
|
-
client = self._create_openai_client(company)
|
|
112
|
-
elif provider == LLMProvider.GEMINI:
|
|
113
|
-
client = self._create_gemini_client(company)
|
|
114
|
-
else:
|
|
115
|
-
raise IAToolkitException(f"provider not supported: {provider.value}")
|
|
116
|
-
|
|
117
|
-
if client:
|
|
118
|
-
LLMProxy._clients_cache[cache_key] = client
|
|
119
|
-
|
|
120
|
-
if not client:
|
|
121
|
-
raise IAToolkitException(IAToolkitException.ErrorType.API_KEY, f"No se pudo crear el cliente para {provider.value}")
|
|
122
|
-
|
|
123
|
-
return client
|
|
124
|
-
|
|
125
|
-
def _create_openai_client(self, company: Company) -> OpenAI:
|
|
126
|
-
"""Crea un cliente de OpenAI con la API key."""
|
|
127
|
-
decrypted_api_key = ''
|
|
128
|
-
llm_config = self.configuration_service.get_configuration(company.short_name, 'llm')
|
|
129
|
-
|
|
130
|
-
# Try to get API key name from config first
|
|
131
|
-
if llm_config and llm_config.get('api-key'):
|
|
132
|
-
api_key_env_var = llm_config['api-key']
|
|
133
|
-
decrypted_api_key = os.getenv(api_key_env_var, '')
|
|
134
|
-
else:
|
|
135
|
-
# Fallback to old logic
|
|
136
|
-
if company.openai_api_key:
|
|
137
|
-
decrypted_api_key = self.util.decrypt_key(company.openai_api_key)
|
|
138
|
-
else:
|
|
139
|
-
decrypted_api_key = os.getenv("OPENAI_API_KEY", '')
|
|
140
|
-
|
|
141
|
-
if not decrypted_api_key:
|
|
142
|
-
raise IAToolkitException(IAToolkitException.ErrorType.API_KEY,
|
|
143
|
-
f"La empresa '{company.name}' no tiene API key de OpenAI.")
|
|
144
|
-
return OpenAI(api_key=decrypted_api_key)
|
|
145
|
-
|
|
146
|
-
def _create_gemini_client(self, company: Company) -> Any:
|
|
147
|
-
"""Configura y devuelve el cliente de Gemini."""
|
|
148
|
-
|
|
149
|
-
decrypted_api_key = ''
|
|
150
|
-
llm_config = self.configuration_service.get_configuration(company.short_name, 'llm')
|
|
151
|
-
|
|
152
|
-
# Try to get API key name from config first
|
|
153
|
-
if llm_config and llm_config.get('api-key'):
|
|
154
|
-
api_key_env_var = llm_config['api-key']
|
|
155
|
-
decrypted_api_key = os.getenv(api_key_env_var, '')
|
|
156
|
-
else:
|
|
157
|
-
# Fallback to old logic
|
|
158
|
-
if company.gemini_api_key:
|
|
159
|
-
decrypted_api_key = self.util.decrypt_key(company.gemini_api_key)
|
|
160
|
-
else:
|
|
161
|
-
decrypted_api_key = os.getenv("GEMINI_API_KEY", '')
|
|
215
|
+
raise IAToolkitException(
|
|
216
|
+
IAToolkitException.ErrorType.MODEL,
|
|
217
|
+
f"Provider not supported in _create_client_for_provider: {provider}"
|
|
218
|
+
)
|
|
162
219
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
220
|
+
# -------------------------------------------------------------------------
|
|
221
|
+
# Configuration helpers
|
|
222
|
+
# -------------------------------------------------------------------------
|
|
223
|
+
def _get_api_key_from_config(self, company_short_name: str, provider: str) -> str:
|
|
224
|
+
"""
|
|
225
|
+
Read the LLM API key from company configuration and environment variables.
|
|
226
|
+
|
|
227
|
+
Resolución de prioridad:
|
|
228
|
+
1. llm.provider_api_keys[provider] -> env var específica por proveedor.
|
|
229
|
+
2. llm.api-key -> env var global (compatibilidad hacia atrás).
|
|
230
|
+
"""
|
|
231
|
+
llm_config = self.configuration_service.get_configuration(company_short_name, "llm")
|
|
232
|
+
|
|
233
|
+
if not llm_config:
|
|
234
|
+
# Mantener compatibilidad con los tests: el mensaje debe indicar
|
|
235
|
+
# que no hay API key configurada.
|
|
236
|
+
raise IAToolkitException(
|
|
237
|
+
IAToolkitException.ErrorType.API_KEY,
|
|
238
|
+
f"Company '{company_short_name}' doesn't have an API key configured."
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
provider_keys = llm_config.get("provider_api_keys") or {}
|
|
242
|
+
env_var_name = None
|
|
243
|
+
|
|
244
|
+
# 1) Intentar api-key específica por proveedor (si existe el bloque provider_api_keys)
|
|
245
|
+
if provider_keys and isinstance(provider_keys, dict):
|
|
246
|
+
env_var_name = provider_keys.get(provider)
|
|
247
|
+
|
|
248
|
+
# 2) Fallback: usar api-key global si no hay específica
|
|
249
|
+
if not env_var_name and llm_config.get("api-key"):
|
|
250
|
+
env_var_name = llm_config["api-key"]
|
|
251
|
+
|
|
252
|
+
if not env_var_name:
|
|
253
|
+
raise IAToolkitException(
|
|
254
|
+
IAToolkitException.ErrorType.API_KEY,
|
|
255
|
+
f"Company '{company_short_name}' doesn't have an API key configured "
|
|
256
|
+
f"for provider '{provider}'."
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
api_key_value = os.getenv(env_var_name, "")
|
|
260
|
+
|
|
261
|
+
if not api_key_value:
|
|
262
|
+
raise IAToolkitException(
|
|
263
|
+
IAToolkitException.ErrorType.API_KEY,
|
|
264
|
+
f"Environment variable '{env_var_name}' for company '{company_short_name}' "
|
|
265
|
+
f"and provider '{provider}' is not set or is empty."
|
|
266
|
+
)
|
|
167
267
|
|
|
268
|
+
return api_key_value
|
iatoolkit/infra/llm_response.py
CHANGED
|
@@ -32,9 +32,14 @@ class LLMResponse:
|
|
|
32
32
|
output_text: str
|
|
33
33
|
output: List[ToolCall] # lista de tool calls
|
|
34
34
|
usage: Usage
|
|
35
|
+
reasoning_content: str = None # campo opcional para Chain of Thought
|
|
36
|
+
|
|
35
37
|
|
|
36
38
|
def __post_init__(self):
|
|
37
39
|
"""Asegura que output sea una lista"""
|
|
38
40
|
if self.output is None:
|
|
39
41
|
self.output = []
|
|
40
42
|
|
|
43
|
+
if self.reasoning_content is None:
|
|
44
|
+
self.reasoning_content = ""
|
|
45
|
+
|
iatoolkit/locales/en.yaml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
ui:
|
|
3
3
|
login_widget:
|
|
4
4
|
title: "Sign In"
|
|
5
|
-
welcome_message: "Enter your credentials or register to access
|
|
5
|
+
welcome_message: "Enter your credentials or register to access this platform."
|
|
6
6
|
email_placeholder: "Email address"
|
|
7
7
|
password_placeholder: "Password"
|
|
8
8
|
login_button: "Login"
|
|
@@ -42,8 +42,81 @@ ui:
|
|
|
42
42
|
|
|
43
43
|
chat:
|
|
44
44
|
welcome_message: "Hello! How can I help you today?"
|
|
45
|
-
input_placeholder: "Type
|
|
45
|
+
input_placeholder: "Type here..."
|
|
46
46
|
prompts_available: "Available prompts"
|
|
47
|
+
init_context: "Initializing the context ..."
|
|
48
|
+
|
|
49
|
+
admin:
|
|
50
|
+
workspace: "Workspace"
|
|
51
|
+
configuration: "Configuration"
|
|
52
|
+
company_config: "Company Configuration (company.yaml)"
|
|
53
|
+
prompts: "Prompts"
|
|
54
|
+
prompts_description: "System Prompts"
|
|
55
|
+
knowledge: "Knowledge Base"
|
|
56
|
+
knowledge_rag: "RAG (Vector)"
|
|
57
|
+
knowledge_static: "Static Context"
|
|
58
|
+
schemas: "Schemas"
|
|
59
|
+
schemas_description: "Data Definitions (YAML)"
|
|
60
|
+
context_description: "Static Markdown"
|
|
61
|
+
administration: "Administration"
|
|
62
|
+
monitoring: "Monitoring"
|
|
63
|
+
teams: "Team"
|
|
64
|
+
billing: "Billing"
|
|
65
|
+
company: "Company"
|
|
66
|
+
context: "Context"
|
|
67
|
+
files: "Files"
|
|
68
|
+
select_file: "Select a file for editing"
|
|
69
|
+
select_category: "Select a category"
|
|
70
|
+
editing: "Editing"
|
|
71
|
+
no_file_selected: "No file selected"
|
|
72
|
+
new: "New"
|
|
73
|
+
confirm: "Confirm"
|
|
74
|
+
cancel: "Cancel"
|
|
75
|
+
new_file: "New file"
|
|
76
|
+
delete_file: "Delete file"
|
|
77
|
+
rename_file: "Rename file"
|
|
78
|
+
saved_ok: "File saved successfully"
|
|
79
|
+
save_file: "Save"
|
|
80
|
+
credentials: "Email and password required for login."
|
|
81
|
+
saved_ok: "Saved successfully"
|
|
82
|
+
deleted_ok: "Deleted successfully"
|
|
83
|
+
user_manager: "User management"
|
|
84
|
+
admin_page_title: "Admin access"
|
|
85
|
+
load_configuration: "Save configuration"
|
|
86
|
+
goto_chat: "Go to chat"
|
|
87
|
+
logout: "Close session"
|
|
88
|
+
error_loading: "Error loading file content"
|
|
89
|
+
loading: "Loading..."
|
|
90
|
+
|
|
91
|
+
rag:
|
|
92
|
+
ingestion: "Ingestion"
|
|
93
|
+
ingestion_description: "Ingest documents into the knowledge base."
|
|
94
|
+
workbench: "Workbench"
|
|
95
|
+
documents: "Documents"
|
|
96
|
+
retrieval_lab: "Retrieval Lab."
|
|
97
|
+
retrieval_description: "Test semantic search and context retrieval."
|
|
98
|
+
query_placeholder: "Enter a question to query the knowledge base..."
|
|
99
|
+
search_button: "Search"
|
|
100
|
+
filter: "Filter"
|
|
101
|
+
search_results_title: "Ready to test"
|
|
102
|
+
search_results_description: "Results will appear here"
|
|
103
|
+
filename: "Filename"
|
|
104
|
+
filename_placeholder: "Contains..."
|
|
105
|
+
user: "User"
|
|
106
|
+
status: "Status"
|
|
107
|
+
all_status: "All statuses"
|
|
108
|
+
status_active: "Active"
|
|
109
|
+
status_pending: "Pending"
|
|
110
|
+
status_processing: "Processing"
|
|
111
|
+
status_failed: "Failed"
|
|
112
|
+
created_at: "Created"
|
|
113
|
+
date_range: "Date range"
|
|
114
|
+
delete_confirmation: "Delete File?"
|
|
115
|
+
delete_message: "This action cannot be undone. The file will be permanently removed."
|
|
116
|
+
delete_button: "Delete"
|
|
117
|
+
delete_cancel: "Cancel"
|
|
118
|
+
|
|
119
|
+
|
|
47
120
|
|
|
48
121
|
tooltips:
|
|
49
122
|
history: "History of my queries"
|
|
@@ -52,6 +125,7 @@ ui:
|
|
|
52
125
|
usage_guide: "Usage Guide"
|
|
53
126
|
onboarding: "How to ask better questions"
|
|
54
127
|
logout: "Log out"
|
|
128
|
+
preferences: "Preferences"
|
|
55
129
|
use_prompt_assistant: "Use Prompt Assistant"
|
|
56
130
|
attach_files: "Attach files"
|
|
57
131
|
view_attached_files: "View attached files"
|
|
@@ -86,12 +160,14 @@ errors:
|
|
|
86
160
|
session_creation_failed: "Could not create user session."
|
|
87
161
|
authentication_required: "Authentication required. No session cookie or API Key provided."
|
|
88
162
|
invalid_api_key: "Invalid or inactive API Key."
|
|
163
|
+
api_key_name_required: "api_key_name parameter is required."
|
|
164
|
+
|
|
89
165
|
no_user_identifier_api: "No user_identifier provided for API call."
|
|
166
|
+
no_company_permissions: "Do not have permissions to admin this company."
|
|
90
167
|
templates:
|
|
91
168
|
company_not_found: "Company not found."
|
|
92
169
|
home_template_not_found: "The home page template for the company '{company_short_name}' is not configured."
|
|
93
170
|
template_not_found: "Template not found: '{template_name}'."
|
|
94
|
-
|
|
95
171
|
processing_error: "An error occurred while processing the custom home page template: {error}"
|
|
96
172
|
general:
|
|
97
173
|
unexpected_error: "An unexpected error has occurred. Please contact support."
|
|
@@ -130,7 +206,9 @@ errors:
|
|
|
130
206
|
user_not_authorized: "user is not authorized for this company"
|
|
131
207
|
account_not_verified: "Your account has not been verified. Please check your email."
|
|
132
208
|
missing_response_id: "Can not found 'previous_response_id' for '{company_short_name}/{user_identifier}'. Reinit context"
|
|
133
|
-
|
|
209
|
+
context_rebuild_failed: "Company context rebuild failed."
|
|
210
|
+
cannot_read_excel: "Cannot read Excel file"
|
|
211
|
+
cannot_read_csv: "Cannot read CSV file"
|
|
134
212
|
|
|
135
213
|
api_responses:
|
|
136
214
|
context_reloaded_success: "The context has been successfully reloaded."
|
|
@@ -138,12 +216,27 @@ api_responses:
|
|
|
138
216
|
services:
|
|
139
217
|
mail_sent: "Email sent successfully."
|
|
140
218
|
start_query: "Hello, what can I help you with today?"
|
|
219
|
+
mail_change_password: "mail sent for password change"
|
|
141
220
|
|
|
221
|
+
rag:
|
|
222
|
+
ingestion:
|
|
223
|
+
duplicate: "Document '{filename}' already exists for company '{company_short_name}'. Skipping ingestion."
|
|
224
|
+
failed: "Failed to ingest document: {error}"
|
|
225
|
+
processing_failed: "Processing failed: {error}"
|
|
226
|
+
empty_text: "Extracted text is empty."
|
|
227
|
+
search:
|
|
228
|
+
query_required: "Query is required."
|
|
229
|
+
company_not_found: "Company '{company_short_name}' not found."
|
|
230
|
+
management:
|
|
231
|
+
delete_success: "Document deleted."
|
|
232
|
+
not_found: "Document not found."
|
|
233
|
+
action_not_found: "Action '{action}' not found."
|
|
142
234
|
|
|
143
235
|
flash_messages:
|
|
144
236
|
password_changed_success: "Your password has been successfully reset. You can now log in."
|
|
145
237
|
login_required: "Please log in to continue."
|
|
146
238
|
signup_success: "Registration successful. Please check your email to verify your account."
|
|
239
|
+
signup_success_no_verification: "Registration successful."
|
|
147
240
|
user_associated_success: "Existing user successfully associated with the new company."
|
|
148
241
|
account_verified_success: "Your account has been successfully verified. Welcome!"
|
|
149
242
|
forgot_password_success: "If your email is registered, you will receive a link to reset your password."
|
|
@@ -165,3 +258,40 @@ js_messages:
|
|
|
165
258
|
loading: "Loading..."
|
|
166
259
|
reload_init: "init reloading context in background..."
|
|
167
260
|
no_history_found: "No query history found."
|
|
261
|
+
example: "Example:"
|
|
262
|
+
show_reasoning: "Show reasoning"
|
|
263
|
+
unsaved: "Modified (unsaved)"
|
|
264
|
+
select_file: "Select a file from the list"
|
|
265
|
+
no_file_selected: "No file selected"
|
|
266
|
+
select_company: "Select company"
|
|
267
|
+
delete_ok: "File deleted successfully"
|
|
268
|
+
delete_failed: "File deletion failed"
|
|
269
|
+
rename_ok: "File rename successfully"
|
|
270
|
+
file_created: "File created successfully"
|
|
271
|
+
not_saved: 'Could not save'
|
|
272
|
+
saved_ok: "Saved successfully"
|
|
273
|
+
error_saving: "Error saving file"
|
|
274
|
+
invalid_file_name: "Invalid filename. Use only letters, numbers, underscores, hyphens, and dots."
|
|
275
|
+
config_loaded: "Configuration loaded successfully."
|
|
276
|
+
config_load_error: "Error loading configuration."
|
|
277
|
+
search_placeholder: "Search users..."
|
|
278
|
+
showing: "Showing"
|
|
279
|
+
records: "Records"
|
|
280
|
+
db_user: "User"
|
|
281
|
+
db_role: "Role"
|
|
282
|
+
db_verified: "Verified"
|
|
283
|
+
db_created: "Created"
|
|
284
|
+
db_last_access: "Last access"
|
|
285
|
+
db_filename: "Filename"
|
|
286
|
+
db_user: "User"
|
|
287
|
+
db_status: "Status"
|
|
288
|
+
db_collection: "Collection"
|
|
289
|
+
editor_no_file_selected: "No file selected"
|
|
290
|
+
error_loading: "Error loading file content"
|
|
291
|
+
cant_load_company: "Could not load company.yaml"
|
|
292
|
+
config_saved: "Configuration saved successfully."
|
|
293
|
+
config_error: "Error saving configuration."
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|