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.
- 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 +43 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +47 -5
- iatoolkit/common/util.py +32 -13
- iatoolkit/company_registry.py +5 -0
- iatoolkit/core.py +51 -20
- iatoolkit/infra/connectors/file_connector_factory.py +1 -0
- iatoolkit/infra/connectors/s3_connector.py +4 -2
- 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 +158 -2
- iatoolkit/locales/es.yaml +158 -0
- iatoolkit/repositories/database_manager.py +52 -47
- 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 +126 -53
- iatoolkit/services/configuration_service.py +299 -73
- 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 +425 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
- iatoolkit/services/load_documents_service.py +26 -48
- 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 +122 -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 +45 -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/configuration_api_view.py +163 -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.7.0.dist-info}/METADATA +4 -4
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/RECORD +66 -58
- 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.7.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/top_level.txt +0 -0
iatoolkit/__init__.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
__version__ = "
|
|
6
|
+
__version__ = "1.7.0"
|
|
7
7
|
|
|
8
8
|
# Expose main classes and functions at the top level of the package
|
|
9
9
|
|
|
@@ -17,13 +17,14 @@ from .base_company import BaseCompany
|
|
|
17
17
|
# --- Services ---
|
|
18
18
|
from iatoolkit.services.query_service import QueryService
|
|
19
19
|
from iatoolkit.services.document_service import DocumentService
|
|
20
|
-
from iatoolkit.services.
|
|
20
|
+
from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
|
|
21
21
|
from iatoolkit.services.sql_service import SqlService
|
|
22
22
|
from iatoolkit.services.load_documents_service import LoadDocumentsService
|
|
23
23
|
from iatoolkit.infra.call_service import CallServiceClient
|
|
24
24
|
from iatoolkit.services.profile_service import ProfileService
|
|
25
25
|
from iatoolkit.services.mail_service import MailService
|
|
26
26
|
from iatoolkit.repositories.models import Base as OrmModel
|
|
27
|
+
from iatoolkit.base_company import BaseCompany
|
|
27
28
|
|
|
28
29
|
__all__ = [
|
|
29
30
|
'IAToolkit',
|
|
@@ -35,10 +36,11 @@ __all__ = [
|
|
|
35
36
|
'QueryService',
|
|
36
37
|
'SqlService',
|
|
37
38
|
'DocumentService',
|
|
38
|
-
'
|
|
39
|
+
'KnowledgeBaseService',
|
|
39
40
|
'LoadDocumentsService',
|
|
40
41
|
'CallServiceClient',
|
|
41
42
|
'ProfileService',
|
|
42
43
|
'MailService',
|
|
43
|
-
'OrmModel'
|
|
44
|
+
'OrmModel',
|
|
45
|
+
'BaseCompany',
|
|
44
46
|
]
|
iatoolkit/base_company.py
CHANGED
|
@@ -5,21 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
# companies/base_company.py
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
|
-
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
10
|
-
from iatoolkit.repositories.models import Company
|
|
11
|
-
from iatoolkit.core import IAToolkit
|
|
12
|
-
|
|
13
8
|
|
|
14
9
|
class BaseCompany(ABC):
|
|
15
|
-
def __init__(self):
|
|
16
|
-
# Obtener el inyector global y resolver las dependencias internamente
|
|
17
|
-
injector = IAToolkit.get_instance().get_injector()
|
|
18
|
-
self.profile_repo: ProfileRepo = injector.get(ProfileRepo)
|
|
19
|
-
self.llm_query_repo: LLMQueryRepo = injector.get(LLMQueryRepo)
|
|
20
|
-
self.company: Company | None = None
|
|
21
|
-
self.company_short_name = ''
|
|
22
|
-
|
|
23
10
|
|
|
24
11
|
@abstractmethod
|
|
25
12
|
# execute the specific action configured in the intent table
|
|
@@ -28,9 +15,6 @@ class BaseCompany(ABC):
|
|
|
28
15
|
|
|
29
16
|
@abstractmethod
|
|
30
17
|
def register_cli_commands(self, app):
|
|
31
|
-
"""
|
|
32
|
-
optional method for a company definition of it's cli commands
|
|
33
|
-
"""
|
|
34
18
|
pass
|
|
35
19
|
|
|
36
20
|
def unsupported_operation(self, tag):
|
iatoolkit/cli_commands.py
CHANGED
|
@@ -14,12 +14,13 @@ def register_core_commands(app):
|
|
|
14
14
|
|
|
15
15
|
@app.cli.command("api-key")
|
|
16
16
|
@click.argument("company_short_name")
|
|
17
|
-
|
|
17
|
+
@click.argument("key_name")
|
|
18
|
+
def api_key(company_short_name: str, key_name: str):
|
|
18
19
|
"""⚙️ Genera una nueva API key para una compañía ya registrada."""
|
|
19
20
|
try:
|
|
20
21
|
profile_service = IAToolkit.get_instance().get_injector().get(ProfileService)
|
|
21
22
|
click.echo(f"🔑 Generating API-KEY for company: '{company_short_name}'...")
|
|
22
|
-
result = profile_service.new_api_key(company_short_name)
|
|
23
|
+
result = profile_service.new_api_key(company_short_name, key_name)
|
|
23
24
|
|
|
24
25
|
if 'error' in result:
|
|
25
26
|
click.echo(f"❌ Error: {result['error']}")
|
|
@@ -44,17 +45,5 @@ def register_core_commands(app):
|
|
|
44
45
|
logging.exception(e)
|
|
45
46
|
click.echo(f"Error: {str(e)}")
|
|
46
47
|
|
|
47
|
-
@app.cli.command("exec-tasks")
|
|
48
|
-
@click.argument("company_short_name")
|
|
49
|
-
def exec_pending_tasks(company_short_name: str):
|
|
50
|
-
from iatoolkit.services.tasks_service import TaskService
|
|
51
|
-
task_service = IAToolkit.get_instance().get_injector().get(TaskService)
|
|
52
|
-
|
|
53
|
-
try:
|
|
54
|
-
result = task_service.trigger_pending_tasks(company_short_name)
|
|
55
|
-
click.echo(result['message'])
|
|
56
|
-
except Exception as e:
|
|
57
|
-
logging.exception(e)
|
|
58
|
-
click.echo(f"Error: {str(e)}")
|
|
59
48
|
|
|
60
49
|
|
iatoolkit/common/exceptions.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List
|
|
3
|
+
import abc
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AssetType(Enum):
|
|
7
|
+
CONFIG = "config"
|
|
8
|
+
PROMPT = "prompts"
|
|
9
|
+
SCHEMA = "schema"
|
|
10
|
+
CONTEXT = "context"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AssetRepository(abc.ABC):
|
|
14
|
+
@abc.abstractmethod
|
|
15
|
+
def exists(self, company_short_name: str, asset_type: AssetType, filename: str) -> bool:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abc.abstractmethod
|
|
19
|
+
def read_text(self, company_short_name: str, asset_type: AssetType, filename: str) -> str:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
def list_files(self, company_short_name: str, asset_type: AssetType, extension: str = None) -> List[str]:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def write_text(self, company_short_name: str, asset_type: AssetType, filename: str, content: str) -> None:
|
|
28
|
+
"""Creates or updates a text asset."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abc.abstractmethod
|
|
32
|
+
def delete(self, company_short_name: str, asset_type: AssetType, filename: str) -> None:
|
|
33
|
+
"""Deletes an asset if it exists."""
|
|
34
|
+
pass
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Any, List, Dict, Union
|
|
3
|
+
|
|
4
|
+
class DatabaseProvider(abc.ABC):
|
|
5
|
+
"""
|
|
6
|
+
Abstract interface for interacting with a database source.
|
|
7
|
+
Handles both metadata introspection and query execution.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# --- Schema Methods ---
|
|
11
|
+
@abc.abstractmethod
|
|
12
|
+
def get_database_structure(self) -> dict:
|
|
13
|
+
"""
|
|
14
|
+
Returns the structure of the database (tables, columns, types)
|
|
15
|
+
Format:
|
|
16
|
+
{
|
|
17
|
+
"table_name": {
|
|
18
|
+
"columns": [
|
|
19
|
+
{"name": "col1", "type": "VARCHAR", "nullable": True, "pk": True},
|
|
20
|
+
...
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
# --- Execution Methods ---
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def execute_query(self, query: str, commit: bool = False) -> Union[List[Dict[str, Any]], Dict[str, int]]:
|
|
30
|
+
"""
|
|
31
|
+
Executes a query and returns:
|
|
32
|
+
- A list of dicts for SELECT (rows).
|
|
33
|
+
- A dict {'rowcount': N} for INSERT/UPDATE/DELETE.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@abc.abstractmethod
|
|
38
|
+
def commit(self) -> None:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abc.abstractmethod
|
|
42
|
+
def rollback(self) -> None:
|
|
43
|
+
pass
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
7
|
+
# Product: IAToolkit
|
|
8
|
+
#
|
|
9
|
+
# IAToolkit is open source software.
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from injector import inject, singleton
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
HistoryType = Literal["server_side", "client_side"]
|
|
19
|
+
ProviderType = Literal["openai", "gemini", "deepseek", "xai", "anthropic", "unknown"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class ModelMetadata:
|
|
24
|
+
"""Static metadata for a logical family of models."""
|
|
25
|
+
provider: ProviderType
|
|
26
|
+
history_type: HistoryType
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@singleton
|
|
30
|
+
class ModelRegistry:
|
|
31
|
+
"""
|
|
32
|
+
Central registry for model metadata.
|
|
33
|
+
|
|
34
|
+
Responsibilities:
|
|
35
|
+
- Map a model name to its provider (openai, gemini, deepseek, etc.).
|
|
36
|
+
- Decide which history strategy to use for a model (server_side / client_side).
|
|
37
|
+
- Provide convenience helpers (is_openai, is_gemini, is_deepseek, etc.).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@inject
|
|
41
|
+
def __init__(self):
|
|
42
|
+
# Hardcoded rules for now; can be extended or loaded from config later.
|
|
43
|
+
# The order of patterns matters: first match wins.
|
|
44
|
+
self._provider_patterns: dict[ProviderType, tuple[str, ...]] = {
|
|
45
|
+
"openai": ("gpt", "gpt-5", "gpt-5-mini", "gpt-5.1"),
|
|
46
|
+
"gemini": ("gemini", "gemini-3"),
|
|
47
|
+
"deepseek": ("deepseek",),
|
|
48
|
+
"xai": ("grok", "grok-1", "grok-beta"),
|
|
49
|
+
"anthropic": ("claude", "claude-3", "claude-2"),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ------------------------------------------------------------------
|
|
53
|
+
# Public API
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
def get_provider(self, model: str) -> ProviderType:
|
|
57
|
+
"""
|
|
58
|
+
Returns the logical provider for a given model name.
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
"gpt-4o" -> "openai"
|
|
62
|
+
"gemini-pro" -> "gemini"
|
|
63
|
+
"deepseek-chat" -> "deepseek"
|
|
64
|
+
"""
|
|
65
|
+
if not model:
|
|
66
|
+
return "unknown"
|
|
67
|
+
|
|
68
|
+
model_lower = model.lower()
|
|
69
|
+
for provider, patterns in self._provider_patterns.items():
|
|
70
|
+
if any(pat in model_lower for pat in patterns):
|
|
71
|
+
return provider
|
|
72
|
+
|
|
73
|
+
return "unknown"
|
|
74
|
+
|
|
75
|
+
def get_request_defaults(self, model: str) -> dict:
|
|
76
|
+
"""
|
|
77
|
+
Return per-model request defaults to keep model-specific policy centralized.
|
|
78
|
+
|
|
79
|
+
Notes:
|
|
80
|
+
- This should only include keys that are supported by the target provider.
|
|
81
|
+
- Callers should merge these defaults with user-provided params (do not mutate inputs).
|
|
82
|
+
"""
|
|
83
|
+
model_lower = (model or "").lower()
|
|
84
|
+
provider = self.get_provider(model_lower)
|
|
85
|
+
|
|
86
|
+
# Conservative defaults: do not send provider-specific knobs unless we know they are supported.
|
|
87
|
+
defaults = {"text": {}, "reasoning": {}}
|
|
88
|
+
|
|
89
|
+
# OpenAI/xAI (OpenAI-compatible) support 'text.verbosity' and 'reasoning.effort' in our current integration.
|
|
90
|
+
if provider in ("openai", "xai"):
|
|
91
|
+
defaults["text"] = {"verbosity": "low"}
|
|
92
|
+
|
|
93
|
+
# Fine-grained per-model tuning.
|
|
94
|
+
if model_lower in ("gpt-5", "gpt-5-mini"):
|
|
95
|
+
defaults["reasoning"] = {"effort": "minimal"}
|
|
96
|
+
elif model_lower == "gpt-5.1":
|
|
97
|
+
defaults["reasoning"] = {"effort": "low", "summary": "auto"}
|
|
98
|
+
|
|
99
|
+
# Gemini/DeepSeek/unknown: keep defaults empty to avoid sending unsupported parameters.
|
|
100
|
+
return defaults
|
|
101
|
+
|
|
102
|
+
def resolve_request_params(self, model: str, text: dict | None = None, reasoning: dict | None = None) -> dict:
|
|
103
|
+
"""
|
|
104
|
+
Resolve provider/model defaults and merge them with caller-provided overrides.
|
|
105
|
+
|
|
106
|
+
Rules:
|
|
107
|
+
- Defaults come from get_request_defaults(model).
|
|
108
|
+
- Caller overrides win over defaults.
|
|
109
|
+
- Input dictionaries are never mutated.
|
|
110
|
+
"""
|
|
111
|
+
defaults = self.get_request_defaults(model)
|
|
112
|
+
|
|
113
|
+
merged_text: dict = {}
|
|
114
|
+
merged_text.update(defaults.get("text") or {})
|
|
115
|
+
merged_text.update(text or {})
|
|
116
|
+
|
|
117
|
+
merged_reasoning: dict = {}
|
|
118
|
+
merged_reasoning.update(defaults.get("reasoning") or {})
|
|
119
|
+
merged_reasoning.update(reasoning or {})
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
"text": merged_text,
|
|
123
|
+
"reasoning": merged_reasoning,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def get_history_type(self, model: str) -> HistoryType:
|
|
127
|
+
"""
|
|
128
|
+
Returns the history strategy for a given model.
|
|
129
|
+
|
|
130
|
+
Current rules:
|
|
131
|
+
- openai/xai/anthropic: server_side (API manages conversation state via ids)
|
|
132
|
+
- gemini/deepseek/unknown: client_side (we manage full message history)
|
|
133
|
+
"""
|
|
134
|
+
provider = self.get_provider(model)
|
|
135
|
+
|
|
136
|
+
if provider in ("openai", "xai", "anthropic"):
|
|
137
|
+
return "server_side"
|
|
138
|
+
|
|
139
|
+
# Default for gemini, deepseek and any unknown provider
|
|
140
|
+
return "client_side"
|
|
141
|
+
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
# Convenience helpers (used during migration)
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
def is_openai_model(self, model: str) -> bool:
|
|
147
|
+
return self.get_provider(model) == "openai"
|
|
148
|
+
|
|
149
|
+
def is_gemini_model(self, model: str) -> bool:
|
|
150
|
+
return self.get_provider(model) == "gemini"
|
|
151
|
+
|
|
152
|
+
def is_deepseek_model(self, model: str) -> bool:
|
|
153
|
+
return self.get_provider(model) == "deepseek"
|
|
154
|
+
|
|
155
|
+
def is_xai_model(self, model: str) -> bool:
|
|
156
|
+
return self.get_provider(model) == "xai"
|
|
157
|
+
|
|
158
|
+
def is_anthropic_model(self, model: str) -> bool:
|
|
159
|
+
return self.get_provider(model) == "anthropic"
|
iatoolkit/common/routes.py
CHANGED
|
@@ -12,8 +12,6 @@ def register_views(app):
|
|
|
12
12
|
|
|
13
13
|
from iatoolkit.views.init_context_api_view import InitContextApiView
|
|
14
14
|
from iatoolkit.views.llmquery_api_view import LLMQueryApiView
|
|
15
|
-
from iatoolkit.views.tasks_api_view import TaskApiView
|
|
16
|
-
from iatoolkit.views.tasks_review_api_view import TaskReviewApiView
|
|
17
15
|
from iatoolkit.views.signup_view import SignupView
|
|
18
16
|
from iatoolkit.views.verify_user_view import VerifyAccountView
|
|
19
17
|
from iatoolkit.views.forgot_password_view import ForgotPasswordView
|
|
@@ -26,10 +24,14 @@ def register_views(app):
|
|
|
26
24
|
from iatoolkit.views.profile_api_view import UserLanguageApiView
|
|
27
25
|
from iatoolkit.views.embedding_api_view import EmbeddingApiView
|
|
28
26
|
from iatoolkit.views.login_view import LoginView, FinalizeContextView
|
|
27
|
+
from iatoolkit.views.configuration_api_view import ConfigurationApiView, ValidateConfigurationApiView
|
|
29
28
|
from iatoolkit.views.logout_api_view import LogoutApiView
|
|
30
29
|
from iatoolkit.views.home_view import HomeView
|
|
30
|
+
from iatoolkit.views.chat_view import ChatView
|
|
31
31
|
from iatoolkit.views.static_page_view import StaticPageView
|
|
32
32
|
from iatoolkit.views.root_redirect_view import RootRedirectView
|
|
33
|
+
from iatoolkit.views.users_api_view import UsersApiView
|
|
34
|
+
from iatoolkit.views.rag_api_view import RagApiView
|
|
33
35
|
|
|
34
36
|
# assign root '/' to our new redirect logic
|
|
35
37
|
app.add_url_rule('/home', view_func=RootRedirectView.as_view('root_redirect'))
|
|
@@ -40,6 +42,10 @@ def register_views(app):
|
|
|
40
42
|
# login for the iatoolkit integrated frontend
|
|
41
43
|
app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
|
|
42
44
|
|
|
45
|
+
# Chat Route (Direct Access)
|
|
46
|
+
app.add_url_rule('/<company_short_name>/chat',
|
|
47
|
+
view_func=ChatView.as_view('chat'))
|
|
48
|
+
|
|
43
49
|
# this endpoint is called when onboarding_shell finish the context load
|
|
44
50
|
app.add_url_rule(
|
|
45
51
|
'/<company_short_name>/finalize',
|
|
@@ -70,6 +76,10 @@ def register_views(app):
|
|
|
70
76
|
app.add_url_rule('/<company_short_name>/verify/<token>', view_func=VerifyAccountView.as_view('verify_account'))
|
|
71
77
|
app.add_url_rule('/<company_short_name>/forgot-password', view_func=ForgotPasswordView.as_view('forgot_password'))
|
|
72
78
|
app.add_url_rule('/<company_short_name>/change-password/<token>', view_func=ChangePasswordView.as_view('change_password'))
|
|
79
|
+
app.add_url_rule(
|
|
80
|
+
'/<string:company_short_name>/api/company-users',
|
|
81
|
+
view_func=UsersApiView.as_view('company-users')
|
|
82
|
+
)
|
|
73
83
|
|
|
74
84
|
# main chat query, used by the JS in the browser (with credentials)
|
|
75
85
|
# can be used also for executing iatoolkit prompts
|
|
@@ -83,9 +93,32 @@ def register_views(app):
|
|
|
83
93
|
app.add_url_rule('/<company_short_name>/api/history', view_func=HistoryApiView.as_view('history'))
|
|
84
94
|
app.add_url_rule('/<company_short_name>/api/help-content', view_func=HelpContentApiView.as_view('help-content'))
|
|
85
95
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
# --- RAG API Routes ---
|
|
97
|
+
rag_view = RagApiView.as_view('rag_api')
|
|
98
|
+
|
|
99
|
+
# 1. List Files (POST for filters)
|
|
100
|
+
app.add_url_rule('/api/rag/<company_short_name>/files',
|
|
101
|
+
view_func=rag_view,
|
|
102
|
+
methods=['POST'],
|
|
103
|
+
defaults={'action': 'list_files'})
|
|
104
|
+
|
|
105
|
+
# 2. Delete File
|
|
106
|
+
app.add_url_rule('/api/rag/<company_short_name>/files/<int:document_id>',
|
|
107
|
+
view_func=rag_view,
|
|
108
|
+
methods=['DELETE'],
|
|
109
|
+
defaults={'action': 'delete_file'})
|
|
110
|
+
|
|
111
|
+
# 3. Search Lab
|
|
112
|
+
app.add_url_rule('/api/rag/<company_short_name>/search',
|
|
113
|
+
view_func=rag_view,
|
|
114
|
+
methods=['POST'],
|
|
115
|
+
defaults={'action': 'search'})
|
|
116
|
+
|
|
117
|
+
# 4. Get File Content (View/Download)
|
|
118
|
+
app.add_url_rule('/api/rag/<company_short_name>/files/<int:document_id>/content',
|
|
119
|
+
view_func=rag_view,
|
|
120
|
+
methods=['GET'],
|
|
121
|
+
defaults={'action': 'get_file_content'})
|
|
89
122
|
|
|
90
123
|
# this endpoint is for upload documents into the vector store (api-key)
|
|
91
124
|
app.add_url_rule('/api/load-document', view_func=LoadDocumentApiView.as_view('load-document'), methods=['POST'])
|
|
@@ -94,6 +127,15 @@ def register_views(app):
|
|
|
94
127
|
app.add_url_rule('/<company_short_name>/api/embedding',
|
|
95
128
|
view_func=EmbeddingApiView.as_view('embedding_api'))
|
|
96
129
|
|
|
130
|
+
# company configuration
|
|
131
|
+
app.add_url_rule('/<company_short_name>/api/configuration',
|
|
132
|
+
view_func=ConfigurationApiView.as_view('configuration'),
|
|
133
|
+
methods=['GET', 'POST', 'PATCH'],)
|
|
134
|
+
|
|
135
|
+
app.add_url_rule('/<company_short_name>/api/configuration/validate',
|
|
136
|
+
view_func=ValidateConfigurationApiView.as_view('configuration-validate'),
|
|
137
|
+
methods=['GET'])
|
|
138
|
+
|
|
97
139
|
# static pages
|
|
98
140
|
# url: /pages/foundation o /pages/implementation_plan
|
|
99
141
|
static_view = StaticPageView.as_view('static_pages')
|
iatoolkit/common/util.py
CHANGED
|
@@ -157,6 +157,31 @@ class Utility:
|
|
|
157
157
|
schema = yaml.safe_load(f)
|
|
158
158
|
return schema
|
|
159
159
|
|
|
160
|
+
def load_yaml_from_string(self, yaml_content: str) -> dict:
|
|
161
|
+
"""
|
|
162
|
+
Parses a YAML string into a dictionary securely.
|
|
163
|
+
"""
|
|
164
|
+
try:
|
|
165
|
+
yaml_content = yaml_content.replace('\t', ' ')
|
|
166
|
+
return yaml.safe_load(yaml_content) or {}
|
|
167
|
+
except yaml.YAMLError as e:
|
|
168
|
+
logging.error(f"Error parsing YAML string: {e}")
|
|
169
|
+
return {}
|
|
170
|
+
|
|
171
|
+
def dump_yaml_to_string(self, config: dict) -> str:
|
|
172
|
+
"""
|
|
173
|
+
Dumps a dictionary into a YAML formatted string.
|
|
174
|
+
It uses default flow style False to ensure block format (readable YAML).
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
# default_flow_style=False ensures lists and dicts are expanded (not inline like JSON)
|
|
178
|
+
# allow_unicode=True ensures characters like accents are preserved
|
|
179
|
+
return yaml.safe_dump(config, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
180
|
+
except yaml.YAMLError as e:
|
|
181
|
+
logging.error(f"Error dumping YAML to string: {e}")
|
|
182
|
+
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
183
|
+
f"Failed to generate YAML: {e}")
|
|
184
|
+
|
|
160
185
|
def generate_context_for_schema(self, entity_name: str, schema_file: str = None, schema: dict = {}) -> str:
|
|
161
186
|
if not schema_file and not schema:
|
|
162
187
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
@@ -186,9 +211,13 @@ class Utility:
|
|
|
186
211
|
root_name = list(schema.keys())[0]
|
|
187
212
|
root_details = schema[root_name]
|
|
188
213
|
|
|
214
|
+
# support this format
|
|
215
|
+
if root_details.get('columns'):
|
|
216
|
+
root_details = root_details['columns']
|
|
217
|
+
|
|
189
218
|
if isinstance(root_details, dict):
|
|
190
219
|
# Las claves de metadatos describen el objeto en sí, no sus propiedades hijas.
|
|
191
|
-
METADATA_KEYS = ['description', 'type', 'format', 'items', 'properties']
|
|
220
|
+
METADATA_KEYS = ['description', 'type', 'format', 'items', 'properties', 'pk']
|
|
192
221
|
|
|
193
222
|
# Las propiedades son las claves restantes en el diccionario.
|
|
194
223
|
properties = {
|
|
@@ -231,6 +260,8 @@ class Utility:
|
|
|
231
260
|
description = details.get('description', '')
|
|
232
261
|
data_type = details.get('type', 'any')
|
|
233
262
|
output.append(f"{indent_str}- **`{name.lower()}`** ({data_type}): {description}")
|
|
263
|
+
# if 'pk' in details and details['pk']:
|
|
264
|
+
# output.append(f"{indent_str}- **Primary Key**: {details['pk']}")
|
|
234
265
|
|
|
235
266
|
child_indent_str = ' ' * (indent_level + 1)
|
|
236
267
|
|
|
@@ -340,15 +371,3 @@ class Utility:
|
|
|
340
371
|
logging.exception(e)
|
|
341
372
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
342
373
|
f'Error al buscar archivos en el directorio {directory}: {str(e)}') from e
|
|
343
|
-
|
|
344
|
-
def is_openai_model(self, model: str) -> bool:
|
|
345
|
-
openai_models = [
|
|
346
|
-
'gpt-5', 'gpt'
|
|
347
|
-
]
|
|
348
|
-
return any(openai_model in model.lower() for openai_model in openai_models)
|
|
349
|
-
|
|
350
|
-
def is_gemini_model(self, model: str) -> bool:
|
|
351
|
-
gemini_models = [
|
|
352
|
-
'gemini', 'gemini-2.5-pro', 'gemini-3'
|
|
353
|
-
]
|
|
354
|
-
return any(gemini_model in model.lower() for gemini_model in gemini_models)
|
iatoolkit/company_registry.py
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
from typing import Dict, Type, Any, Optional
|
|
7
7
|
from .base_company import BaseCompany
|
|
8
8
|
import logging
|
|
9
|
+
from injector import inject
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class CompanyRegistry:
|
|
@@ -14,6 +15,7 @@ class CompanyRegistry:
|
|
|
14
15
|
Allow the client to register companies and instantiate them with dependency injection.
|
|
15
16
|
"""
|
|
16
17
|
|
|
18
|
+
@inject
|
|
17
19
|
def __init__(self):
|
|
18
20
|
self._company_classes: Dict[str, Type[BaseCompany]] = {}
|
|
19
21
|
self._company_instances: Dict[str, BaseCompany] = {}
|
|
@@ -85,6 +87,9 @@ def get_company_registry() -> CompanyRegistry:
|
|
|
85
87
|
"""Get the global company registry instance."""
|
|
86
88
|
return _company_registry
|
|
87
89
|
|
|
90
|
+
def get_registered_companies() -> Dict[str, Type[BaseCompany]]:
|
|
91
|
+
return _company_registry.get_registered_companies()
|
|
92
|
+
|
|
88
93
|
|
|
89
94
|
def set_company_registry(registry: CompanyRegistry) -> None:
|
|
90
95
|
"""
|