iatoolkit 0.66.2__py3-none-any.whl → 0.71.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.
Files changed (73) hide show
  1. iatoolkit/__init__.py +2 -6
  2. iatoolkit/base_company.py +3 -31
  3. iatoolkit/cli_commands.py +1 -1
  4. iatoolkit/common/routes.py +5 -1
  5. iatoolkit/common/session_manager.py +2 -0
  6. iatoolkit/company_registry.py +1 -2
  7. iatoolkit/iatoolkit.py +13 -13
  8. iatoolkit/infra/llm_client.py +8 -12
  9. iatoolkit/infra/llm_proxy.py +38 -10
  10. iatoolkit/locales/en.yaml +25 -2
  11. iatoolkit/locales/es.yaml +27 -4
  12. iatoolkit/repositories/database_manager.py +8 -3
  13. iatoolkit/repositories/document_repo.py +1 -1
  14. iatoolkit/repositories/models.py +6 -8
  15. iatoolkit/repositories/profile_repo.py +0 -4
  16. iatoolkit/repositories/vs_repo.py +26 -20
  17. iatoolkit/services/auth_service.py +2 -2
  18. iatoolkit/services/branding_service.py +11 -7
  19. iatoolkit/services/company_context_service.py +155 -0
  20. iatoolkit/services/configuration_service.py +133 -0
  21. iatoolkit/services/dispatcher_service.py +75 -70
  22. iatoolkit/services/document_service.py +5 -2
  23. iatoolkit/services/embedding_service.py +146 -0
  24. iatoolkit/services/excel_service.py +15 -11
  25. iatoolkit/services/file_processor_service.py +4 -12
  26. iatoolkit/services/history_service.py +7 -7
  27. iatoolkit/services/i18n_service.py +4 -4
  28. iatoolkit/services/jwt_service.py +7 -9
  29. iatoolkit/services/language_service.py +29 -23
  30. iatoolkit/services/load_documents_service.py +100 -113
  31. iatoolkit/services/mail_service.py +9 -4
  32. iatoolkit/services/profile_service.py +10 -7
  33. iatoolkit/services/prompt_manager_service.py +20 -16
  34. iatoolkit/services/query_service.py +112 -43
  35. iatoolkit/services/search_service.py +11 -4
  36. iatoolkit/services/sql_service.py +57 -25
  37. iatoolkit/services/user_feedback_service.py +15 -13
  38. iatoolkit/static/js/chat_history_button.js +3 -5
  39. iatoolkit/static/js/chat_main.js +2 -17
  40. iatoolkit/static/js/chat_onboarding_button.js +6 -0
  41. iatoolkit/static/styles/chat_iatoolkit.css +69 -158
  42. iatoolkit/static/styles/chat_modal.css +1 -37
  43. iatoolkit/static/styles/onboarding.css +7 -0
  44. iatoolkit/system_prompts/query_main.prompt +2 -10
  45. iatoolkit/templates/change_password.html +1 -1
  46. iatoolkit/templates/chat.html +12 -4
  47. iatoolkit/templates/chat_modals.html +4 -0
  48. iatoolkit/templates/error.html +1 -1
  49. iatoolkit/templates/login_simulation.html +17 -6
  50. iatoolkit/templates/onboarding_shell.html +4 -1
  51. iatoolkit/views/base_login_view.py +7 -8
  52. iatoolkit/views/change_password_view.py +2 -3
  53. iatoolkit/views/embedding_api_view.py +65 -0
  54. iatoolkit/views/external_login_view.py +1 -1
  55. iatoolkit/views/file_store_api_view.py +1 -1
  56. iatoolkit/views/forgot_password_view.py +2 -4
  57. iatoolkit/views/help_content_api_view.py +9 -9
  58. iatoolkit/views/history_api_view.py +1 -1
  59. iatoolkit/views/home_view.py +2 -2
  60. iatoolkit/views/init_context_api_view.py +18 -17
  61. iatoolkit/views/llmquery_api_view.py +3 -2
  62. iatoolkit/views/login_simulation_view.py +14 -2
  63. iatoolkit/views/login_view.py +9 -9
  64. iatoolkit/views/signup_view.py +2 -4
  65. iatoolkit/views/verify_user_view.py +2 -4
  66. {iatoolkit-0.66.2.dist-info → iatoolkit-0.71.2.dist-info}/METADATA +40 -22
  67. iatoolkit-0.71.2.dist-info/RECORD +122 -0
  68. iatoolkit-0.71.2.dist-info/licenses/LICENSE +21 -0
  69. iatoolkit/services/help_content_service.py +0 -30
  70. iatoolkit/services/onboarding_service.py +0 -43
  71. iatoolkit-0.66.2.dist-info/RECORD +0 -119
  72. {iatoolkit-0.66.2.dist-info → iatoolkit-0.71.2.dist-info}/WHEEL +0 -0
  73. {iatoolkit-0.66.2.dist-info → iatoolkit-0.71.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,65 @@
1
+ # iatoolkit/views/embedding_api_view.py
2
+ # Copyright (c) 2024 Fernando Libedinsky
3
+ # Product: IAToolkit
4
+ #
5
+ # IAToolkit is open source software.
6
+
7
+ from flask import request, jsonify
8
+ from flask.views import MethodView
9
+ from iatoolkit.services.embedding_service import EmbeddingService
10
+ from iatoolkit.services.auth_service import AuthService
11
+ from injector import inject
12
+ import logging
13
+
14
+ class EmbeddingApiView(MethodView):
15
+ """
16
+ Handles API requests to generate an embedding for a given text.
17
+ Authentication is based on the active Flask session.
18
+ """
19
+
20
+ @inject
21
+ def __init__(self,
22
+ auth_service: AuthService,
23
+ embedding_service: EmbeddingService):
24
+ self.auth_service = auth_service
25
+ self.embedding_service = embedding_service
26
+
27
+ def post(self, company_short_name: str):
28
+ """
29
+ Generates an embedding for the text provided in the request body.
30
+ Expects a JSON payload with a "text" key.
31
+ """
32
+ try:
33
+ # 1. Authenticate the user from the current session
34
+ auth_result = self.auth_service.verify(anonymous=True)
35
+ if not auth_result.get("success"):
36
+ return jsonify(auth_result), auth_result.get("status_code", 401)
37
+
38
+ # 2. Validate incoming request data
39
+ if not request.is_json:
40
+ return jsonify({"error": "Request must be JSON"}), 400
41
+
42
+ data = request.get_json()
43
+ text = data.get('text')
44
+
45
+ if not text:
46
+ return jsonify({"error": "The 'text' key is required."}), 400
47
+
48
+ # 3. Call the embedding service, now passing the company_short_name
49
+ embedding_b64 = self.embedding_service.embed_text(
50
+ company_short_name=company_short_name,
51
+ text=text,
52
+ to_base64=True
53
+ )
54
+
55
+ model_name = self.embedding_service.get_model_name(company_short_name)
56
+ response = {
57
+ "embedding": embedding_b64,
58
+ "model": model_name
59
+ }
60
+ return jsonify(response), 200
61
+
62
+ except Exception as e:
63
+ logging.exception(f"Unexpected error in EmbeddingApiView: {e}")
64
+ # Return a generic error message to the client
65
+ return jsonify({"error": "An internal error occurred while generating the embedding."}), 500
@@ -47,7 +47,7 @@ class ExternalLoginView(BaseLoginView):
47
47
 
48
48
  # 5. Delegate the path decision to the centralized logic.
49
49
  try:
50
- return self._handle_login_path(company, user_identifier, target_url, redeem_token)
50
+ return self._handle_login_path(company_short_name, user_identifier, target_url, redeem_token)
51
51
  except Exception as e:
52
52
  logging.exception(f"Error processing external login path for {company_short_name}/{user_identifier}: {e}")
53
53
  return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
@@ -48,7 +48,7 @@ class FileStoreApiView(MethodView):
48
48
  # get the file content from base64
49
49
  content = base64.b64decode(base64_content)
50
50
 
51
- new_document = self.doc_service.load_file_callback(
51
+ new_document = self.doc_service._file_processing_callback(
52
52
  filename=filename,
53
53
  content=content,
54
54
  company=company,
@@ -30,9 +30,8 @@ class ForgotPasswordView(MethodView):
30
30
  return render_template('error.html',
31
31
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
32
32
 
33
- branding_data = self.branding_service.get_company_branding(company)
33
+ branding_data = self.branding_service.get_company_branding(company_short_name)
34
34
  return render_template('forgot_password.html',
35
- company=company,
36
35
  company_short_name=company_short_name,
37
36
  branding=branding_data
38
37
  )
@@ -45,7 +44,7 @@ class ForgotPasswordView(MethodView):
45
44
  return render_template('error.html',
46
45
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
47
46
 
48
- branding_data = self.branding_service.get_company_branding(company)
47
+ branding_data = self.branding_service.get_company_branding(company_short_name)
49
48
  email = request.form.get('email')
50
49
 
51
50
  # create a safe token and url for it
@@ -59,7 +58,6 @@ class ForgotPasswordView(MethodView):
59
58
  flash(response["error"], 'error')
60
59
  return render_template(
61
60
  'forgot_password.html',
62
- company=company,
63
61
  company_short_name=company_short_name,
64
62
  branding=branding_data,
65
63
  form_data={"email": email}), 400
@@ -5,7 +5,7 @@
5
5
 
6
6
  from flask import request, jsonify
7
7
  from flask.views import MethodView
8
- from iatoolkit.services.help_content_service import HelpContentService
8
+ from iatoolkit.services.configuration_service import ConfigurationService
9
9
  from iatoolkit.services.i18n_service import I18nService
10
10
  from iatoolkit.services.auth_service import AuthService
11
11
  from injector import inject
@@ -21,10 +21,10 @@ class HelpContentApiView(MethodView):
21
21
  @inject
22
22
  def __init__(self,
23
23
  auth_service: AuthService,
24
- help_content_service: HelpContentService,
24
+ config_service: ConfigurationService,
25
25
  i18n_service: I18nService):
26
26
  self.auth_service = auth_service
27
- self.help_content_service = help_content_service
27
+ self.config_service = config_service
28
28
  self.i18n_service = i18n_service
29
29
 
30
30
  def post(self, company_short_name: str):
@@ -36,10 +36,10 @@ class HelpContentApiView(MethodView):
36
36
 
37
37
  user_identifier = auth_result.get('user_identifier')
38
38
 
39
- # 2. Call the history service with the unified identifier.
40
- # The service's signature should now only expect user_identifier.
41
- response = self.help_content_service.get_content(
42
- company_short_name=company_short_name
39
+ # 2. Call the config service with the unified identifier.
40
+ response = self.config_service.get_configuration(
41
+ company_short_name=company_short_name,
42
+ content_key='help_content' # specific key for this service
43
43
  )
44
44
 
45
45
  if "error" in response:
@@ -50,5 +50,5 @@ class HelpContentApiView(MethodView):
50
50
 
51
51
  except Exception as e:
52
52
  logging.exception(
53
- f"Unexpected error fetching help_content for {company_short_name}/{user_identifier}: {e}")
54
- return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error')}), 500
53
+ f"Unexpected error fetching help_content for {company_short_name}: {e}")
54
+ return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
@@ -53,4 +53,4 @@ class HistoryApiView(MethodView):
53
53
  except Exception as e:
54
54
  logging.exception(
55
55
  f"Unexpected error: {e}")
56
- return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error')}), 500
56
+ return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
@@ -25,13 +25,14 @@ class HomeView(MethodView):
25
25
  self.util = utility
26
26
 
27
27
  def get(self, company_short_name: str):
28
+ branding_data = {}
28
29
  try:
29
30
  company = self.profile_service.get_company_by_short_name(company_short_name)
30
31
  if not company:
31
32
  return render_template('error.html',
32
33
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
33
34
 
34
- branding_data = self.branding_service.get_company_branding(company)
35
+ branding_data = self.branding_service.get_company_branding(company_short_name)
35
36
  home_template = self.util.get_company_template(company_short_name, "home.html")
36
37
 
37
38
  # 2. Verificamos si el archivo de plantilla personalizado no existe.
@@ -47,7 +48,6 @@ class HomeView(MethodView):
47
48
  # 3. Si el archivo existe, intentamos leerlo y renderizarlo.
48
49
  return render_template_string(
49
50
  home_template,
50
- company=company,
51
51
  company_short_name=company_short_name,
52
52
  branding=branding_data,
53
53
  )
@@ -4,7 +4,7 @@ from iatoolkit.services.query_service import QueryService
4
4
  from iatoolkit.services.profile_service import ProfileService
5
5
  from iatoolkit.services.auth_service import AuthService
6
6
  from iatoolkit.services.i18n_service import I18nService
7
- from flask import jsonify
7
+ from flask import jsonify, request
8
8
  import logging
9
9
 
10
10
 
@@ -39,29 +39,30 @@ class InitContextApiView(MethodView):
39
39
 
40
40
  user_identifier = auth_result.get('user_identifier')
41
41
 
42
- # 2. Execute the forced rebuild sequence using the unified identifier.
43
- self.query_service.session_context.clear_all_context(company_short_name, user_identifier)
44
- logging.info(f"Context for {company_short_name}/{user_identifier} has been cleared.")
42
+ # check if model was sent as a parameter
43
+ data = request.get_json(silent=True) or {}
44
+ model = data.get('model', '')
45
45
 
46
- # LLM context is clean, now we can load it again
47
- self.query_service.prepare_context(
46
+ # reinit the LLM context
47
+ response = self.query_service.init_context(
48
48
  company_short_name=company_short_name,
49
- user_identifier=user_identifier
50
- )
49
+ user_identifier=user_identifier,
50
+ model=model)
51
51
 
52
- self.query_service.finalize_context_rebuild(
53
- company_short_name=company_short_name,
54
- user_identifier=user_identifier
55
- )
56
-
57
- # 3. Respond with JSON, as this is an API endpoint.
52
+ # Respond with JSON, as this is an API endpoint.
58
53
  success_message = self.i18n_service.t('api_responses.context_reloaded_success')
59
- return jsonify({'status': 'OK', 'message': success_message}), 200
54
+ response_message = {'status': 'OK', 'message': success_message}
55
+
56
+ # if received a response ID with the context, return it
57
+ if response.get('response_id'):
58
+ response_message['response_id'] = response['response_id']
59
+
60
+ return jsonify(response_message), 200
60
61
 
61
62
  except Exception as e:
62
63
  logging.exception(f"errors while reloading context: {e}")
63
- error_message = self.i18n_service.t('errors.general.unexpected_error')
64
- return jsonify({"error_message": error_message}), 500
64
+ error_message = self.i18n_service.t('errors.general.unexpected_error', error=str(e))
65
+ return jsonify({"error_message": error_message}), 406
65
66
 
66
67
  def options(self, company_short_name):
67
68
  """
@@ -44,14 +44,15 @@ class LLMQueryApiView(MethodView):
44
44
  question=data.get('question', ''),
45
45
  prompt_name=data.get('prompt_name'),
46
46
  client_data=data.get('client_data', {}),
47
+ response_id = data.get('response_id'),
47
48
  files=data.get('files', [])
48
49
  )
49
50
  if 'error' in result:
50
- return jsonify(result), 400
51
+ return jsonify(result), 407
51
52
 
52
53
  return jsonify(result), 200
53
54
 
54
55
  except Exception as e:
55
56
  logging.exception(
56
57
  f"Unexpected error: {e}")
57
- return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error')}), 500
58
+ return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
@@ -10,17 +10,29 @@ from flask.views import MethodView
10
10
  from flask import render_template, request, Response
11
11
  from injector import inject
12
12
  from iatoolkit.services.profile_service import ProfileService
13
+ from iatoolkit.services.branding_service import BrandingService
13
14
 
14
15
 
15
16
  class LoginSimulationView(MethodView):
16
17
  @inject
17
18
  def __init__(self,
18
- profile_service: ProfileService):
19
+ profile_service: ProfileService,
20
+ branding_service: BrandingService):
19
21
  self.profile_service = profile_service
22
+ self.branding_service = branding_service
23
+
20
24
 
21
25
  def get(self, company_short_name: str = None):
22
- """Muestra el formulario para iniciar la simulación."""
26
+ company = self.profile_service.get_company_by_short_name(company_short_name)
27
+ if not company:
28
+ return render_template('error.html',
29
+ company_short_name=company_short_name,
30
+ message="Empresa no encontrada"), 404
31
+
32
+ branding_data = self.branding_service.get_company_branding(company_short_name)
33
+
23
34
  return render_template('login_simulation.html',
35
+ branding=branding_data,
24
36
  company_short_name=company_short_name
25
37
  )
26
38
 
@@ -12,7 +12,7 @@ from iatoolkit.services.jwt_service import JWTService
12
12
  from iatoolkit.services.query_service import QueryService
13
13
  from iatoolkit.services.prompt_manager_service import PromptService
14
14
  from iatoolkit.services.branding_service import BrandingService
15
- from iatoolkit.services.onboarding_service import OnboardingService
15
+ from iatoolkit.services.configuration_service import ConfigurationService
16
16
  from iatoolkit.services.i18n_service import I18nService
17
17
  from iatoolkit.views.base_login_view import BaseLoginView
18
18
  import logging
@@ -29,7 +29,7 @@ class LoginView(BaseLoginView):
29
29
  return render_template('error.html',
30
30
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
31
31
 
32
- branding_data = self.branding_service.get_company_branding(company)
32
+ branding_data = self.branding_service.get_company_branding(company_short_name)
33
33
  email = request.form.get('email')
34
34
  password = request.form.get('password')
35
35
 
@@ -61,7 +61,7 @@ class LoginView(BaseLoginView):
61
61
 
62
62
  # 2. Delegate the path decision to the centralized logic.
63
63
  try:
64
- return self._handle_login_path(company, user_identifier, target_url)
64
+ return self._handle_login_path(company_short_name, user_identifier, target_url)
65
65
  except Exception as e:
66
66
  message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
67
67
  return render_template(
@@ -83,7 +83,7 @@ class FinalizeContextView(MethodView):
83
83
  query_service: QueryService,
84
84
  prompt_service: PromptService,
85
85
  branding_service: BrandingService,
86
- onboarding_service: OnboardingService,
86
+ config_service: ConfigurationService,
87
87
  jwt_service: JWTService,
88
88
  i18n_service: I18nService
89
89
  ):
@@ -92,7 +92,7 @@ class FinalizeContextView(MethodView):
92
92
  self.query_service = query_service
93
93
  self.prompt_service = prompt_service
94
94
  self.branding_service = branding_service
95
- self.onboarding_service = onboarding_service
95
+ self.config_service = config_service
96
96
  self.i18n_service = i18n_service
97
97
 
98
98
  def get(self, company_short_name: str, token: str = None):
@@ -111,7 +111,7 @@ class FinalizeContextView(MethodView):
111
111
 
112
112
  user_identifier = payload.get('user_identifier')
113
113
  else:
114
- logging.warning("Fallo crítico: missing session information or auth token")
114
+ logging.error("missing session information or auth token")
115
115
  return redirect(url_for('home', company_short_name=company_short_name))
116
116
 
117
117
  company = self.profile_service.get_company_by_short_name(company_short_name)
@@ -119,17 +119,17 @@ class FinalizeContextView(MethodView):
119
119
  return render_template('error.html',
120
120
  company_short_name=company_short_name,
121
121
  message="Empresa no encontrada"), 404
122
- branding_data = self.branding_service.get_company_branding(company)
122
+ branding_data = self.branding_service.get_company_branding(company_short_name)
123
123
 
124
124
  # 2. Finalize the context rebuild (the heavy task).
125
- self.query_service.finalize_context_rebuild(
125
+ self.query_service.set_context_for_llm(
126
126
  company_short_name=company_short_name,
127
127
  user_identifier=user_identifier
128
128
  )
129
129
 
130
130
  # 3. render the chat page.
131
131
  prompts = self.prompt_service.get_user_prompts(company_short_name)
132
- onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
132
+ onboarding_cards = self.config_service.get_configuration(company_short_name, 'onboarding_cards')
133
133
 
134
134
  # Get the entire 'js_messages' block in the correct language.
135
135
  js_translations = self.i18n_service.get_translation_block('js_messages')
@@ -32,9 +32,8 @@ class SignupView(MethodView):
32
32
  return render_template('error.html',
33
33
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
34
34
 
35
- branding_data = self.branding_service.get_company_branding(company)
35
+ branding_data = self.branding_service.get_company_branding(company_short_name)
36
36
  return render_template('signup.html',
37
- company=company,
38
37
  company_short_name=company_short_name,
39
38
  branding=branding_data)
40
39
 
@@ -45,7 +44,7 @@ class SignupView(MethodView):
45
44
  return render_template('error.html',
46
45
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
47
46
 
48
- branding_data = self.branding_service.get_company_branding(company)
47
+ branding_data = self.branding_service.get_company_branding(company_short_name)
49
48
 
50
49
  first_name = request.form.get('first_name')
51
50
  last_name = request.form.get('last_name')
@@ -70,7 +69,6 @@ class SignupView(MethodView):
70
69
  flash(response["error"], 'error')
71
70
  return render_template(
72
71
  'signup.html',
73
- company=company,
74
72
  company_short_name=company_short_name,
75
73
  branding=branding_data,
76
74
  form_data={
@@ -32,14 +32,13 @@ class VerifyAccountView(MethodView):
32
32
  return render_template('error.html',
33
33
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
34
34
 
35
- branding_data = self.branding_service.get_company_branding(company)
35
+ branding_data = self.branding_service.get_company_branding(company_short_name)
36
36
  try:
37
37
  # decode the token from the URL
38
38
  email = self.serializer.loads(token, salt='email-confirm', max_age=3600*5)
39
39
  except SignatureExpired:
40
40
  flash(self.i18n_service.t('errors.verification.token_expired'), 'error')
41
41
  return render_template('signup.html',
42
- company=company,
43
42
  company_short_name=company_short_name,
44
43
  branding=branding_data,
45
44
  token=token), 400
@@ -49,7 +48,6 @@ class VerifyAccountView(MethodView):
49
48
  flash(response["error"], 'error')
50
49
  return render_template(
51
50
  'signup.html',
52
- company=company,
53
51
  company_short_name=company_short_name,
54
52
  branding=branding_data,
55
53
  token=token), 400
@@ -58,5 +56,5 @@ class VerifyAccountView(MethodView):
58
56
  return redirect(url_for('home', company_short_name=company_short_name))
59
57
 
60
58
  except Exception as e:
61
- flash(self.i18n_service.t('errors.general.unexpected_error'), 'error')
59
+ flash(self.i18n_service.t('errors.general.unexpected_error', error=str(e)), 'error')
62
60
  return redirect(url_for('home', company_short_name=company_short_name))
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.66.2
3
+ Version: 0.71.2
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
7
7
  Requires-Python: >=3.12
8
8
  Description-Content-Type: text/markdown
9
+ License-File: LICENSE
9
10
  Requires-Dist: aiohappyeyeballs==2.4.4
10
11
  Requires-Dist: aiohttp==3.11.9
11
12
  Requires-Dist: aiosignal==1.3.1
@@ -206,47 +207,64 @@ Requires-Dist: wrapt==1.17.0
206
207
  Requires-Dist: yarl==1.18.3
207
208
  Requires-Dist: zipp==3.21.0
208
209
  Requires-Dist: zstandard==0.23.0
210
+ Dynamic: license-file
209
211
 
210
212
 
211
213
  <div align="center">
212
- <h1>IAToolkit</h1>
214
+ <h1>
215
+ IAToolkit
216
+ </h1>
217
+
213
218
  <p><strong>The Open-Source Framework for Building AI Chatbots on Your Private Data.</strong></p>
219
+ <h4>
220
+ <a href="https://www.iatoolkit.com" target="_blank" style="text-decoration: none; color: inherit;">
221
+ www.iatoolkit.com
222
+ </a>
223
+ </h4>
214
224
  </div>
215
225
 
216
- IAToolkit is a comprehensive, open-source framework designed for building enterprise-grade
217
- AI chatbots and conversational applications.
226
+ IAToolkit is a comprehensive, Python open-source framework designed for building enterprise-grade
227
+ AI chatbots and conversational applications. It bridges the gap between the power of
228
+ Large Language Models (LLMs) and the valuable,
229
+ private data locked within your organization's databases and documents.
230
+
218
231
  With IAToolkit, you can build production-ready, context-aware chatbots and agents that
219
232
  can query relational databases, perform semantic searches on documents,
220
233
  and connect to your internal APIs in minutes.
221
234
 
222
- IAToolkit bridges the gap between powerful LLMs and your company's data.
235
+ Create secure, branded chat interfaces that can reason over your data, answer questions, and execute custom business logic,
236
+ all powered by leading models from OpenAI, Google Gemini, and more.
223
237
 
224
238
 
225
239
  ## 🚀 Key Features
226
240
 
227
- * **🔗 Unified Data Connection**:
228
- * **Natural Language to SQL**: Let your chatbot query relational databases (PostgreSQL, MySQL, SQLite) using everyday language.
229
- * **Semantic Document Search**: Automatically chunk, embed, and search across your private documents (PDFs, Word, etc.) to provide contextually accurate answers.
241
+ * **🔗 Unified Data Connection**
242
+ * **Natural Language to SQL**: Let your chatbot query relational databases (PostgreSQL, MySQL, SQLite) using everyday language.
243
+ * **Semantic Document Search**: Automatically chunk, embed, and search across your private documents (PDFs, Word, etc.) to provide contextually accurate answers.
230
244
 
231
- * **🏢 Enterprise-Ready Multi-Tenancy**:
232
- * Deploy isolated "Company" modules, each with its own data, tools, and context. Perfect for SaaS products or internal departmental agents.
245
+ * **🏢 Enterprise-Ready Multi-Tenancy**
246
+ * Deploy isolated "Company" modules, each with its own data, tools, and context.
247
+ * Perfect for SaaS products or internal departmental agents.
233
248
 
234
- * **🧠 LLM Agnostic**:
235
- * Switch between **OpenAI (GPT-*)** and **Google (Gemini-*)** with a single line change in your configuration. No code refactoring needed.
249
+ * **🎨 Fully Brandable UI**
250
+ * Customize the look and feel for each "Company" with its own logos, colors, and even language settings (i18n).
251
+ * Provides a white-labeled experience for your users.
236
252
 
237
- * **🛠️ Developer-First Experience**:
238
- * Built with a clean, **Dependency Injection** architecture.
239
- * High-quality code base with **90%+ test coverage**.
240
- * Powerful Flask-based **CLI** for database setup, API key generation, and more.
253
+ * **🧠 LLM Agnostic**
254
+ * Switch between **OpenAI (GPT-*)** and **Google (Gemini-*)** with a single line change in your configuration.
255
+ * No code refactoring needed.
241
256
 
242
- * **🔒 Security & Observability Built-In**:
243
- * Comes with JWT-based authentication, user management, and secure session handling out of the box.
244
- * Full traceability with detailed logging of all queries, function calls, token usage, and costs.
257
+ * **🛠️ Developer-First Experience**
258
+ * Built with a clean **Dependency Injection** architecture.
259
+ * High-quality code base with **90%+ test coverage**.
245
260
 
246
- ## Quick Start: Create a Custom Tool in 30 Seconds
261
+ * **🔒 Security & Observability Built-In**
262
+ * Comes with integrated user authentication, API keys, and secure session handling out of the box.
263
+ * Full traceability with detailed logging of all queries, function calls, token usage, and costs.
264
+ ## ⚡ Quick Start: Try our 'hello world' example
247
265
 
248
- See how easy it is to give your AI a new skill. Just define a method inside your Company class and describe it.
249
- IAToolkit handles the rest.
266
+ Ready to see it in action? Our Quickstart Guide will walk you through downloading, configuring, and launching your first AI assistant in just a few minutes.
267
+ It's the best way to experience the toolkit's capabilities firsthand.
250
268
 
251
269
  ## 🤝 Contributing
252
270