iatoolkit 2.41.0__tar.gz → 2.41.2__tar.gz

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 (251) hide show
  1. {iatoolkit-2.41.0/src/iatoolkit.egg-info → iatoolkit-2.41.2}/PKG-INFO +1 -1
  2. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/__init__.py +1 -1
  3. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/model_registry.py +52 -0
  4. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/core.py +3 -5
  5. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_proxy.py +56 -10
  6. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/locales/en.yaml +17 -0
  7. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/locales/es.yaml +17 -0
  8. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/models.py +14 -0
  9. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/tenant_wiki_service.py +109 -0
  10. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/warmup_service.py +94 -33
  11. {iatoolkit-2.41.0 → iatoolkit-2.41.2/src/iatoolkit.egg-info}/PKG-INFO +1 -1
  12. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/LICENSE +0 -0
  13. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/LICENSE_COMMUNITY.md +0 -0
  14. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/pyproject.toml +0 -0
  15. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/readme.md +0 -0
  16. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/requirements.txt +0 -0
  17. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/setup.cfg +0 -0
  18. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/base_company.py +0 -0
  19. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/cli_commands.py +0 -0
  20. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/__init__.py +0 -0
  21. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/bridge_validation.py +0 -0
  22. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/exceptions.py +0 -0
  23. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/__init__.py +0 -0
  24. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
  25. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
  26. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
  27. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/memory_lint_trigger.py +0 -0
  28. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
  29. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
  30. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
  31. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/routes.py +0 -0
  32. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/secret_resolver.py +0 -0
  33. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/session_manager.py +0 -0
  34. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/common/util.py +0 -0
  35. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/company_registry.py +0 -0
  36. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/__init__.py +0 -0
  37. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
  38. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
  39. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
  40. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
  41. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
  42. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
  43. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
  44. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
  45. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
  46. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
  47. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
  48. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
  49. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
  50. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
  51. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
  52. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
  53. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
  54. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
  55. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
  56. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
  57. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
  58. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
  59. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
  60. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
  61. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
  62. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/sql_rules.prompt +0 -0
  63. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
  64. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
  65. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/config/system_tools_pack.yaml +0 -0
  66. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/__init__.py +0 -0
  67. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
  68. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/call_service.py +0 -0
  69. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  70. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
  71. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
  72. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
  73. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
  74. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
  75. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
  76. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/google_auth_client.py +0 -0
  77. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/google_chat_app.py +0 -0
  78. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
  79. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/inference_handler.py +0 -0
  80. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
  81. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
  82. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
  83. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
  84. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
  85. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
  86. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
  87. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
  88. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
  89. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/llm_response.py +0 -0
  90. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  91. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/__init__.py +0 -0
  92. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/api_key_repo.py +0 -0
  93. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/bridge_repo.py +0 -0
  94. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/database_manager.py +0 -0
  95. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/document_repo.py +0 -0
  96. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
  97. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
  98. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
  99. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
  100. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
  101. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/memory_repo.py +0 -0
  102. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/profile_repo.py +0 -0
  103. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
  104. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
  105. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
  106. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/repositories/vs_repo.py +0 -0
  107. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/runtime_logging.py +0 -0
  108. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/__init__.py +0 -0
  109. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/api_key_service.py +0 -0
  110. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/attachment_policy_service.py +0 -0
  111. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/auth_service.py +0 -0
  112. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/benchmark_service.py +0 -0
  113. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/branding_service.py +0 -0
  114. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/company_context_service.py +0 -0
  115. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/configuration_service.py +0 -0
  116. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/context_builder_service.py +0 -0
  117. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/dispatcher_service.py +0 -0
  118. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/embedding_service.py +0 -0
  119. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/excel_service.py +0 -0
  120. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/file_processor_service.py +0 -0
  121. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/history_manager_service.py +0 -0
  122. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/http_tool_service.py +0 -0
  123. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/i18n_service.py +0 -0
  124. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/inference_service.py +0 -0
  125. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/jwt_service.py +0 -0
  126. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/knowledge_base_service.py +0 -0
  127. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/language_service.py +0 -0
  128. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/license_service.py +0 -0
  129. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/llm_client_service.py +0 -0
  130. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/mail_service.py +0 -0
  131. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
  132. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/mcp_token_service.py +0 -0
  133. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/memory_compiler_service.py +0 -0
  134. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/memory_lint_service.py +0 -0
  135. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
  136. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/memory_service.py +0 -0
  137. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/memory_wiki_service.py +0 -0
  138. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
  139. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/noop_memory_lint_trigger.py +0 -0
  140. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/__init__.py +0 -0
  141. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/contracts.py +0 -0
  142. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
  143. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
  144. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
  145. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
  146. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
  147. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
  148. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
  149. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
  150. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/parsers/validator.py +0 -0
  151. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/pdf_service.py +0 -0
  152. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/profile_service.py +0 -0
  153. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/prompt_resource_service.py +0 -0
  154. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/prompt_service.py +0 -0
  155. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/query_service.py +0 -0
  156. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
  157. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/sql_dataset_service.py +0 -0
  158. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/sql_service.py +0 -0
  159. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/sql_source_service.py +0 -0
  160. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/storage_service.py +0 -0
  161. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/structured_output_service.py +0 -0
  162. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
  163. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/system_tools.py +0 -0
  164. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/telemetry_service.py +0 -0
  165. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/tool_output_contract.py +0 -0
  166. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/tool_service.py +0 -0
  167. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/user_feedback_service.py +0 -0
  168. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/user_session_context_service.py +0 -0
  169. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/visual_kb_service.py +0 -0
  170. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/visual_tool_service.py +0 -0
  171. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/web_search/__init__.py +0 -0
  172. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
  173. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
  174. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
  175. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/services/web_search_service.py +0 -0
  176. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/images/fernando.jpeg +0 -0
  177. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
  178. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
  179. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
  180. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_filepond.js +0 -0
  181. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_help_content.js +0 -0
  182. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_history_button.js +0 -0
  183. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
  184. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_main.js +0 -0
  185. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
  186. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
  187. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
  188. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
  189. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
  190. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
  191. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  192. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/styles/chat_public.css +0 -0
  193. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/styles/documents.css +0 -0
  194. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/styles/landing_page.css +0 -0
  195. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/styles/llm_output.css +0 -0
  196. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/static/styles/onboarding.css +0 -0
  197. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/_company_header.html +0 -0
  198. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/_login_widget.html +0 -0
  199. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/account.html +0 -0
  200. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/base.html +0 -0
  201. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/change_password.html +0 -0
  202. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/chat.html +0 -0
  203. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
  204. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/chat_modals.html +0 -0
  205. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/error.html +0 -0
  206. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/forgot_password.html +0 -0
  207. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/home_hosted_default.html +0 -0
  208. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
  209. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/onboarding_shell.html +0 -0
  210. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/pdf/base.html +0 -0
  211. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/pdf/letter.html +0 -0
  212. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/pdf/report.html +0 -0
  213. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/pdf/simple.html +0 -0
  214. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/templates/signup.html +0 -0
  215. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/__init__.py +0 -0
  216. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/account_view.py +0 -0
  217. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/api_key_api_view.py +0 -0
  218. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/base_login_view.py +0 -0
  219. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/categories_api_view.py +0 -0
  220. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/change_password_view.py +0 -0
  221. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/chat_context_preview_api_view.py +0 -0
  222. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/chat_view.py +0 -0
  223. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/configuration_api_view.py +0 -0
  224. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/connectors_api_view.py +0 -0
  225. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/embedding_api_view.py +0 -0
  226. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/forgot_password_view.py +0 -0
  227. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/help_content_api_view.py +0 -0
  228. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/history_api_view.py +0 -0
  229. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/home_view.py +0 -0
  230. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/init_context_api_view.py +0 -0
  231. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/llmquery_api_view.py +0 -0
  232. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/load_document_api_view.py +0 -0
  233. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/login_view.py +0 -0
  234. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/logout_api_view.py +0 -0
  235. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/memory_api_view.py +0 -0
  236. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/profile_api_view.py +0 -0
  237. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/prompt_api_view.py +0 -0
  238. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/prompt_context_preview_api_view.py +0 -0
  239. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/prompt_resource_api_view.py +0 -0
  240. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/rag_api_view.py +0 -0
  241. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/root_redirect_view.py +0 -0
  242. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/signup_view.py +0 -0
  243. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/static_page_view.py +0 -0
  244. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/tool_api_view.py +0 -0
  245. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
  246. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/users_api_view.py +0 -0
  247. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit/views/verify_user_view.py +0 -0
  248. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit.egg-info/SOURCES.txt +0 -0
  249. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  250. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit.egg-info/requires.txt +0 -0
  251. {iatoolkit-2.41.0 → iatoolkit-2.41.2}/src/iatoolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 2.41.0
3
+ Version: 2.41.2
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- __version__ = "2.41.0"
6
+ __version__ = "2.41.2"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -46,6 +46,58 @@ SERVABLE_PROVIDERS: tuple[str, ...] = (
46
46
  )
47
47
 
48
48
 
49
+ @dataclass(frozen=True)
50
+ class RouteParam:
51
+ """One thing a provider needs to know before it can be reached.
52
+
53
+ `is_env_name` marks a value that is the *name* of a deployment environment
54
+ variable, not the secret itself: what gets stored is `OSS_LLM_API_KEY`, and the
55
+ value behind it is read at request time and never persisted here.
56
+ """
57
+
58
+ name: str
59
+ required: bool = True
60
+ is_env_name: bool = False
61
+ #: A regular expression the value must match, or None for "any non-empty".
62
+ pattern: str | None = None
63
+
64
+
65
+ #: What each provider needs in order to be reached, keyed by provider.
66
+ #:
67
+ #: The single declaration behind three things: what a catalogue entry may store,
68
+ #: what refuses to publish without it, and which fields a form draws. Kept beside
69
+ #: SERVABLE_PROVIDERS because the two answer halves of the same question — which
70
+ #: providers exist, and what each one needs.
71
+ #:
72
+ #: Most providers need nothing: their SDK knows its own endpoint, and the
73
+ #: credential comes from the company's provider_api_keys mapping. The two that
74
+ #: appear here are the ones whose endpoint is a decision.
75
+ #:
76
+ #: `openai_compatible` requires both, and that is the point of the whole feature:
77
+ #: an entry that carries its own endpoint and credential is reachable on its own,
78
+ #: instead of depending on a block in every company's own configuration file. Two
79
+ #: self-hosted models on two different endpoints stop being impossible.
80
+ #:
81
+ #: `openrouter` allows both and requires neither: there is one OpenRouter for
82
+ #: everyone and its base URL has a working default, so an entry only overrides
83
+ #: when it has a reason to.
84
+ PROVIDER_ROUTE_PARAMS: dict[str, tuple[RouteParam, ...]] = {
85
+ "openai_compatible": (
86
+ RouteParam("base_url", required=True, pattern=r"^https?://\S+$"),
87
+ RouteParam("api_key_env", required=True, is_env_name=True, pattern=r"^[A-Z][A-Z0-9_]*$"),
88
+ ),
89
+ "openrouter": (
90
+ RouteParam("base_url", required=False, pattern=r"^https?://\S+$"),
91
+ RouteParam("api_key_env", required=False, is_env_name=True, pattern=r"^[A-Z][A-Z0-9_]*$"),
92
+ ),
93
+ }
94
+
95
+
96
+ def route_params_for(provider: str | None) -> tuple[RouteParam, ...]:
97
+ """What this provider needs. Empty for the ones that need nothing."""
98
+ return PROVIDER_ROUTE_PARAMS.get(str(provider or "").strip().lower(), ())
99
+
100
+
49
101
  @singleton
50
102
  class ModelRegistry:
51
103
  """
@@ -170,11 +170,9 @@ class IAToolkit:
170
170
  def _run_warmup():
171
171
  try:
172
172
  warmup_service = self._injector.get(WarmupService)
173
- warmed_companies = warmup_service.warmup_startup_configured_companies(trigger="core_startup")
174
- if warmed_companies:
175
- logging.info("🔥 Startup warm-up completed for companies=%s", warmed_companies)
176
- else:
177
- logging.info("🔥 Startup warm-up skipped: no embedding provider enabled warmup_on_startup.")
173
+ woken_endpoints = warmup_service.warmup_startup_configured_companies(trigger="core_startup")
174
+ if woken_endpoints:
175
+ logging.info("🔥 Startup warm-up completed for endpoints=%s", woken_endpoints)
178
176
  except Exception:
179
177
  logging.exception("⚠️ Startup warm-up failed.")
180
178
 
@@ -23,6 +23,7 @@ from iatoolkit.services.telemetry_service import NoopTelemetryService
23
23
  from openai import OpenAI, Timeout # For OpenAI and xAI (OpenAI-compatible)
24
24
 
25
25
  from typing import Dict, List, Any, Tuple
26
+ import os
26
27
  import threading
27
28
  import logging
28
29
  from injector import inject
@@ -586,11 +587,13 @@ class LLMProxy:
586
587
  ) -> dict:
587
588
  provider_config = self.configuration_service.get_llm_provider_config(company_short_name, provider) or {}
588
589
  secret_ref = self._model_credential_ref(model_config)
590
+ route_api_key_env = str(self._model_route(model_config).get("api_key_env") or "").strip() or None
589
591
  provider_api_key = self._get_api_key_from_config(
590
592
  company_short_name,
591
593
  provider,
592
594
  required=False,
593
595
  secret_ref=secret_ref,
596
+ route_api_key_env=route_api_key_env,
594
597
  )
595
598
  gateway_transport = self.gateway_resolver.resolve(
596
599
  company_short_name=company_short_name,
@@ -612,8 +615,9 @@ class LLMProxy:
612
615
  provider,
613
616
  required=True,
614
617
  secret_ref=secret_ref,
618
+ route_api_key_env=route_api_key_env,
615
619
  )
616
- configured_base_url = self._get_base_url_from_config(company_short_name, provider)
620
+ configured_base_url = self._get_base_url_from_config(company_short_name, provider, model_config=model_config)
617
621
  configured_headers = self._get_default_headers_from_config(company_short_name, provider)
618
622
  effective_base_url = gateway_transport.get("base_url") or configured_base_url
619
623
  effective_headers = dict(configured_headers)
@@ -726,6 +730,20 @@ class LLMProxy:
726
730
  # -------------------------------------------------------------------------
727
731
  # Configuration helpers
728
732
  # -------------------------------------------------------------------------
733
+ @staticmethod
734
+ def _model_route(model_config: Dict[str, Any] | None) -> Dict[str, Any]:
735
+ """The endpoint and credential variable this model carries, if any.
736
+
737
+ Comes from the catalogue entry, so two models of the same provider can sit
738
+ on two different endpoints — which is what `openai_compatible` could not do
739
+ while the endpoint lived in each company's own configuration file.
740
+
741
+ Empty means "ask the company's configuration", which is what every model
742
+ did before entries had routes.
743
+ """
744
+ route = (model_config or {}).get("route_config") if isinstance(model_config, dict) else None
745
+ return dict(route) if isinstance(route, dict) else {}
746
+
729
747
  @staticmethod
730
748
  def _model_credential_ref(model_config: Dict[str, Any] | None) -> str | None:
731
749
  """The secret this model is entitled to run on, when the company owns it.
@@ -753,6 +771,7 @@ class LLMProxy:
753
771
  *,
754
772
  required: bool = True,
755
773
  secret_ref: str | None = None,
774
+ route_api_key_env: str | None = None,
756
775
  ) -> str:
757
776
  """
758
777
  Read the LLM API key from company configuration and environment variables.
@@ -774,20 +793,36 @@ class LLMProxy:
774
793
  provider_keys = llm_config.get("provider_api_keys") or {}
775
794
  env_var_name = None
776
795
 
777
- # 0) El secreto de la habilitación del modelo, cuando la empresa trae su
778
- # propia key. Gana sobre el mapeo del archivo porque es la decisión
779
- # explícita y por modelo; el mapeo es del deployment y por proveedor.
780
- # 0) El secreto de la habilitación del modelo, cuando la empresa trae su
781
- # propia key. Gana sobre el mapeo del archivo porque es la decisión
782
- # explícita y por modelo; el mapeo es del deployment y por proveedor.
796
+ # Tres orígenes, en este orden, y el orden es la decisión:
797
+ #
798
+ # 1) El secreto de la habilitación del modelo, cuando la empresa trae su
799
+ # propia key. Es la decisión explícita y por modelo de quién paga, así
800
+ # que gana sobre todo lo demás.
801
+ # 2) La credencial de la ruta del catálogo, leída del entorno del
802
+ # deployment y **no** del store de secretos de la empresa: el endpoint es
803
+ # del operador, y un cliente que guarde un secreto con ese nombre no
804
+ # debería apropiárselo en silencio. Quien quiera pagar con su key usa (1).
805
+ # 3) El mapeo del archivo de la empresa, que es el respaldo para todo lo que
806
+ # no tiene ruta propia — es decir, todo lo que existía antes de esto.
783
807
  if secret_ref:
784
808
  env_var_name = secret_ref
785
809
 
786
- # 1) Intentar api-key específica por proveedor (si existe el bloque provider_api_keys)
810
+ if not env_var_name and route_api_key_env:
811
+ route_value = str(os.getenv(route_api_key_env) or "").strip()
812
+ if route_value:
813
+ return route_value
814
+ if not required:
815
+ return ""
816
+ raise IAToolkitException(
817
+ IAToolkitException.ErrorType.API_KEY,
818
+ f"Environment variable '{route_api_key_env}', named by the catalogue entry "
819
+ f"for provider '{provider}', is not set in this deployment."
820
+ )
821
+
787
822
  if not env_var_name and provider_keys and isinstance(provider_keys, dict):
788
823
  env_var_name = provider_keys.get(provider)
789
824
 
790
- # 2) Fallback: usar api-key global si no hay específica
825
+ # Compatibilidad hacia atrás: api-key global cuando no hay una por proveedor.
791
826
  if not env_var_name and llm_config.get("api-key"):
792
827
  env_var_name = llm_config["api-key"]
793
828
 
@@ -818,10 +853,21 @@ class LLMProxy:
818
853
 
819
854
  return api_key_value
820
855
 
821
- def _get_base_url_from_config(self, company_short_name: str, provider: str) -> str:
856
+ def _get_base_url_from_config(
857
+ self,
858
+ company_short_name: str,
859
+ provider: str,
860
+ model_config: Dict[str, Any] | None = None,
861
+ ) -> str:
822
862
  if provider not in {self.PROVIDER_OPENAI_COMPATIBLE, self.PROVIDER_OPENROUTER}:
823
863
  return ""
824
864
 
865
+ # The entry's own endpoint wins: it is the operator's decision about this
866
+ # model, while the company's configuration is a default for the provider.
867
+ route_base_url = str(self._model_route(model_config).get("base_url") or "").strip()
868
+ if route_base_url:
869
+ return route_base_url
870
+
825
871
  provider_config = self.configuration_service.get_llm_provider_config(company_short_name, provider) or {}
826
872
  if not isinstance(provider_config, dict):
827
873
  provider_config = {}
@@ -703,6 +703,18 @@ ui:
703
703
  mode_external_sync: "External"
704
704
  mode_managed_help: "The wiki is edited from this workspace and pages are saved into storage."
705
705
  mode_external_sync_help: "The wiki stays read-only in IAToolkit and updates from the configured storage root."
706
+ access_control: "Access control"
707
+ access_mode_off: "Off"
708
+ access_mode_off_help: "Access labels are stored but no page is filtered."
709
+ access_mode_dry_run: "Dry run"
710
+ access_mode_dry_run_help: "What would be denied is computed and logged, but every page is still served."
711
+ access_mode_enforce: "Enforced"
712
+ access_mode_enforce_help: "A reader only gets pages whose labels they hold. The admin panel still sees everything."
713
+ access_coverage: "{labelled} of {pages} pages carry access labels."
714
+ access_coverage_gap: "{unlabelled} with no label: nobody sees those while access control is enforced."
715
+ access_mode_ok: "Access control mode updated."
716
+ access_mode_error: "Could not change the access control mode."
717
+ access_mode_confirm: "Once enforced, each user only sees pages whose labels they hold. Continue?"
706
718
  root: "Storage root"
707
719
  edit_root: "Edit storage root"
708
720
  lock_root: "Lock storage root editing"
@@ -2189,6 +2201,11 @@ ui:
2189
2201
  form_secret_value: "Your provider key"
2190
2202
  secret_hint_new: "Stored encrypted for this provider. Your provider bills you directly."
2191
2203
  secret_hint_stored: "A key is already stored for this provider. Paste one to replace it."
2204
+ form_route_base_url: "Endpoint"
2205
+ form_route_base_url_hint: "The full URL requests go to, e.g. https://your-host/v1"
2206
+ form_route_api_key_env: "Credential variable"
2207
+ form_route_api_key_env_hint: "The NAME of an environment variable in this deployment, e.g. OSS_LLM_API_KEY. Never the key itself."
2208
+ route_required: "This provider needs an endpoint and the name of its credential variable."
2192
2209
  secrets:
2193
2210
  title: "Secrets"
2194
2211
  subtitle: "Manage company secrets used by integrations and services."
@@ -705,6 +705,18 @@ ui:
705
705
  mode_external_sync: "Externa"
706
706
  mode_managed_help: "La wiki se edita desde esta interfaz y guarda sus páginas en storage."
707
707
  mode_external_sync_help: "La wiki queda de solo lectura en IAToolkit y se actualiza desde la raíz storage configurada."
708
+ access_control: "Control de acceso"
709
+ access_mode_off: "Desactivado"
710
+ access_mode_off_help: "Las etiquetas de acceso se guardan pero no se filtra ninguna página."
711
+ access_mode_dry_run: "Simulación"
712
+ access_mode_dry_run_help: "Se calcula y registra en el log qué páginas se negarían, pero se siguen entregando todas."
713
+ access_mode_enforce: "Activo"
714
+ access_mode_enforce_help: "Cada lector recibe solo las páginas cuyas etiquetas posee. El panel de administración sigue viendo todo."
715
+ access_coverage: "{labelled} de {pages} páginas con etiquetas de acceso."
716
+ access_coverage_gap: "{unlabelled} sin etiqueta: con el control activo no las ve nadie."
717
+ access_mode_ok: "Modo de control de acceso actualizado."
718
+ access_mode_error: "No se pudo cambiar el modo de control de acceso."
719
+ access_mode_confirm: "Al activarlo, cada usuario verá solo las páginas cuyas etiquetas posee. ¿Continuar?"
708
720
  root: "Raíz storage"
709
721
  edit_root: "Editar raíz storage"
710
722
  lock_root: "Bloquear edición de raíz storage"
@@ -2195,6 +2207,11 @@ ui:
2195
2207
  form_secret_value: "Tu key del proveedor"
2196
2208
  secret_hint_new: "Se guarda cifrada para este proveedor. Tu proveedor te factura directamente."
2197
2209
  secret_hint_stored: "Ya hay una key guardada para este proveedor. Pegá otra para reemplazarla."
2210
+ form_route_base_url: "Endpoint"
2211
+ form_route_base_url_hint: "La URL completa a la que van las requests, ej. https://tu-host/v1"
2212
+ form_route_api_key_env: "Variable de la credencial"
2213
+ form_route_api_key_env_hint: "El NOMBRE de una variable de entorno de este deployment, ej. OSS_LLM_API_KEY. Nunca la key."
2214
+ route_required: "Este proveedor necesita un endpoint y el nombre de la variable con su credencial."
2198
2215
  secrets:
2199
2216
  title: "Secrets"
2200
2217
  subtitle: "Administra los secrets de la empresa usados por integraciones y servicios."
@@ -1241,6 +1241,20 @@ class ModelCatalogEntry(Base):
1241
1241
  model_key = Column(String(120), nullable=False, unique=True, index=True)
1242
1242
  provider = Column(String(60), nullable=False, index=True)
1243
1243
 
1244
+ #: How to reach this model, when reaching it is a decision: the endpoint and
1245
+ #: the name of the environment variable holding its credential.
1246
+ #:
1247
+ #: A JSON object rather than columns because providers do not agree on what
1248
+ #: they need — an Azure deployment wants a deployment name and an api version,
1249
+ #: a gateway may want headers — and `base_url` is meaningless for the
1250
+ #: providers whose SDK knows its own. What may go in here is declared per
1251
+ #: provider in `PROVIDER_ROUTE_PARAMS` and validated on write: an unvalidated
1252
+ #: blob becomes decoration, and a typo in a key is silent.
1253
+ #:
1254
+ #: Empty means "ask the company's own configuration", which is what every
1255
+ #: entry did before this existed.
1256
+ route_config = Column(JSON, nullable=False, default=dict)
1257
+
1244
1258
  display_name = Column(String(120), nullable=False)
1245
1259
  description = Column(Text, nullable=False, default="")
1246
1260
 
@@ -445,6 +445,25 @@ class TenantWikiService:
445
445
  "mcp_index_source_path": self._index_source_display_path(wiki, source_storage_key),
446
446
  "index_path": "/",
447
447
  "index_source_path": self._index_source_display_path(wiki, source_storage_key),
448
+ "access_control": self._access_summary(wiki),
449
+ }
450
+
451
+ def _access_summary(self, wiki: KnowledgeWiki) -> dict:
452
+ """
453
+ The wiki's access control state, for the admin panel: the mode in force
454
+ and how many pages carry labels. A page with none is denied to everyone
455
+ once the mode is enforce, so that count is the number an operator has to
456
+ look at before raising it.
457
+ """
458
+ access_control = self.wiki_access_control(wiki)
459
+ pages = self.knowledge_wiki_repo.list_pages(wiki.id, include_archived=False, limit=self.MAX_PAGES)
460
+ labelled = sum(1 for page in pages if (page.access_tags or []))
461
+ return {
462
+ **access_control,
463
+ "modes": list(self.ACCESS_MODES),
464
+ "pages": len(pages),
465
+ "labelled": labelled,
466
+ "unlabelled": len(pages) - labelled,
448
467
  }
449
468
 
450
469
  def get_page(
@@ -1004,6 +1023,96 @@ class TenantWikiService:
1004
1023
  )
1005
1024
  return None
1006
1025
 
1026
+ def set_access_control_mode(
1027
+ self,
1028
+ company_short_name: str,
1029
+ *,
1030
+ wiki_key: str,
1031
+ mode: str,
1032
+ allow_dark: bool = False,
1033
+ ) -> dict:
1034
+ """
1035
+ Raises or lowers a wiki's access control mode, and nothing else.
1036
+
1037
+ Deliberately not routed through `configure_wiki`: that one rebuilds the
1038
+ wiki from its arguments, so calling it with only settings would rename the
1039
+ wiki and repoint its storage prefix to the default. This touches
1040
+ `settings.access_control` and leaves every other field alone.
1041
+
1042
+ Enforcing a wiki whose pages carry no labels at all would serve nothing
1043
+ to anyone, which is never the intent, so that case is refused unless
1044
+ `allow_dark` says otherwise.
1045
+ """
1046
+ company = self._get_company(company_short_name)
1047
+ if not company:
1048
+ return {"status": "error", "error_message": "company not found"}
1049
+ wiki = self.knowledge_wiki_repo.get_wiki_by_key(company.id, self.normalize_wiki_key(wiki_key))
1050
+ if not wiki:
1051
+ return {"status": "error", "error_message": "wiki not found"}
1052
+
1053
+ requested_mode = str(mode or "").strip().lower()
1054
+ if requested_mode not in self.ACCESS_MODES:
1055
+ # Not normalized silently: an unrecognized mode here would read as
1056
+ # 'off' and quietly turn access control off.
1057
+ return {
1058
+ "status": "error",
1059
+ "error_message": f"mode must be one of {', '.join(self.ACCESS_MODES)}",
1060
+ }
1061
+
1062
+ pages = self.knowledge_wiki_repo.list_pages(wiki.id, include_archived=False, limit=self.MAX_PAGES)
1063
+ labelled = sum(1 for page in pages if (page.access_tags or []))
1064
+ previous = self.wiki_access_control(wiki)
1065
+ if requested_mode == self.ACCESS_MODE_ENFORCE and not labelled and not allow_dark:
1066
+ return {
1067
+ "status": "error",
1068
+ "error_message": (
1069
+ f"wiki '{wiki.wiki_key}' has no page carrying access labels, so enforcing would "
1070
+ "hide all of it from everyone. Sync the access manifest first."
1071
+ ),
1072
+ }
1073
+
1074
+ settings = dict(wiki.settings if isinstance(wiki.settings, dict) else {})
1075
+ # Reassigned rather than mutated in place: an in-place change to a JSON
1076
+ # column is not tracked and would never be written.
1077
+ settings["access_control"] = {
1078
+ "mode": requested_mode,
1079
+ "manifest": previous["manifest"],
1080
+ }
1081
+ wiki.settings = settings
1082
+ self.knowledge_wiki_repo.commit()
1083
+
1084
+ return {
1085
+ "status": "success",
1086
+ "wiki_key": wiki.wiki_key,
1087
+ "previous_mode": previous["mode"],
1088
+ "mode": requested_mode,
1089
+ "manifest": previous["manifest"],
1090
+ "manifest_status": self._probe_access_manifest(company_short_name, wiki, requested_mode),
1091
+ "pages": len(pages),
1092
+ "labelled": labelled,
1093
+ "unlabelled": len(pages) - labelled,
1094
+ }
1095
+
1096
+ def _probe_access_manifest(self, company_short_name: str, wiki: KnowledgeWiki, mode: str) -> str:
1097
+ """
1098
+ Reports whether the manifest could be read right now, without failing.
1099
+
1100
+ Answers the question that matters after raising the mode: would the next
1101
+ sync abort? From dry_run up the manifest is required.
1102
+ """
1103
+ try:
1104
+ _pages, meta = self._load_access_manifest(
1105
+ company_short_name,
1106
+ wiki,
1107
+ {"mode": self.ACCESS_MODE_OFF, "manifest": self.wiki_access_control(wiki)["manifest"]},
1108
+ )
1109
+ except Exception as exc:
1110
+ return f"error: {exc}"
1111
+ status = str(meta.get("manifest_status") or "unknown")
1112
+ if status != "loaded" and mode != self.ACCESS_MODE_OFF:
1113
+ return f"{status} (the next sync will abort while the mode is '{mode}')"
1114
+ return status
1115
+
1007
1116
  def access_visibility_report(
1008
1117
  self,
1009
1118
  company_short_name: str,
@@ -7,6 +7,8 @@ from __future__ import annotations
7
7
 
8
8
  import logging
9
9
  import time
10
+ from urllib.parse import urlparse
11
+
10
12
  from injector import inject
11
13
 
12
14
  from iatoolkit.common.interfaces.secret_provider import SecretProvider
@@ -19,7 +21,12 @@ from iatoolkit.services.embedding_service import EmbeddingService
19
21
  class WarmupService:
20
22
  """
21
23
  Lightweight warm-up orchestrator.
22
- Keep it simple: no shared state, no locking, no dedup.
24
+ Keep it simple: no shared state, no locking.
25
+
26
+ The one exception is the startup path, which deduplicates by resolved
27
+ endpoint URL. That is not coordination between processes — each process
28
+ still knocks on its own — it is not knocking N times for N tenants that
29
+ share one endpoint.
23
30
  """
24
31
 
25
32
  @inject
@@ -57,41 +64,80 @@ class WarmupService:
57
64
  self.warmup_company(company_short_name, trigger=trigger)
58
65
 
59
66
  def warmup_startup_configured_companies(self, trigger: str = "startup") -> list[str]:
60
- warmed_companies: list[str] = []
61
- for company_short_name in get_registered_companies().keys():
62
- profiles = self._get_remote_text_embedding_profiles(company_short_name, startup_only=True)
63
- if not profiles:
64
- logging.info(
65
- "Startup warm-up skipped for company='%s': no embedding provider enabled warmup_on_startup.",
67
+ """Wakes each distinct inference endpoint once. Returns the hosts woken.
68
+
69
+ Once per endpoint, not once per tenant: waking is a property of the
70
+ endpoint, and tenants normally share one. Doing it per tenant only
71
+ multiplied requests against a container that was already starting, each
72
+ one willing to spend the tool's whole retry budget getting there.
73
+
74
+ Nothing is awaited beyond the call. Surviving a cold start is the
75
+ inference client's job, through ``retry_budget_seconds``; this only has
76
+ to knock on the door.
77
+ """
78
+ targets = self._startup_endpoints()
79
+ if not targets:
80
+ logging.info("Startup warm-up skipped: no embedding provider enabled warmup_on_startup.")
81
+ return []
82
+
83
+ woken: list[str] = []
84
+ for endpoint_url, (company_short_name, profile) in targets.items():
85
+ model_type, _config_section, _tool_name = profile
86
+ label = self._format_profile_labels([profile])[0]
87
+ host = self._endpoint_host(endpoint_url)
88
+ try:
89
+ self.embedding_service.embed_text(
66
90
  company_short_name,
91
+ "hello",
92
+ model_type=model_type,
93
+ suppress_error_logging=True,
67
94
  )
68
- continue
69
-
70
- logging.info(
71
- "Startup warm-up starting for company='%s' profiles=%s trigger='%s'.",
72
- company_short_name,
73
- self._format_profile_labels(profiles),
74
- trigger,
75
- )
76
- warmed_profiles = self._warmup_remote_text_embeddings(
77
- company_short_name,
78
- startup_only=True,
79
- profiles=profiles,
80
- )
81
- if not warmed_profiles:
95
+ logging.info(
96
+ "🔥 Startup warm-up woke endpoint='%s' via company='%s' profile=%s trigger='%s'.",
97
+ host, company_short_name, label, trigger,
98
+ )
99
+ woken.append(host)
100
+ except Exception as e:
82
101
  logging.warning(
83
- "Startup warm-up attempted but no embedding profiles warmed for company='%s' profiles=%s.",
84
- company_short_name,
85
- self._format_profile_labels(profiles),
102
+ "Startup warm-up could not wake endpoint='%s' via company='%s' profile=%s: %s",
103
+ host, company_short_name, label, e,
86
104
  )
105
+
106
+ return woken
107
+
108
+ def _startup_endpoints(self) -> dict[str, tuple[str, tuple[str, str, str]]]:
109
+ """One representative (company, profile) per distinct endpoint URL.
110
+
111
+ Keyed by the resolved URL rather than by company or tool name, so a
112
+ deployment where every tenant shares one endpoint knocks once, and a
113
+ tenant later pointed somewhere else gets its own knock without anyone
114
+ having to remember this function exists.
115
+ """
116
+ targets: dict[str, tuple[str, tuple[str, str, str]]] = {}
117
+ for company_short_name in get_registered_companies().keys():
118
+ inference_tools = self.config_service.get_configuration(company_short_name, "inference_tools") or {}
119
+ if not isinstance(inference_tools, dict):
87
120
  continue
88
121
 
89
- logging.info(
90
- "Startup warm-up warmed company='%s' profiles=%s.",
91
- company_short_name,
92
- self._format_profile_labels(warmed_profiles),
93
- )
94
- warmed_companies.append(company_short_name)
122
+ for profile in self._get_remote_text_embedding_profiles(company_short_name, startup_only=True):
123
+ _model_type, _config_section, tool_name = profile
124
+ endpoint_url = self._resolve_endpoint_url(company_short_name, inference_tools, tool_name)
125
+ if not endpoint_url:
126
+ continue
127
+
128
+ existing = targets.get(endpoint_url)
129
+ # Prefer the default text profile as the representative: it is the
130
+ # one rag_search uses, so if the container loads models on demand
131
+ # the one most likely to be asked for first is the one primed.
132
+ if existing is None or (profile[0] == "text" and existing[1][0] != "text"):
133
+ targets[endpoint_url] = (company_short_name, profile)
134
+
135
+ return targets
136
+
137
+ @staticmethod
138
+ def _endpoint_host(endpoint_url: str) -> str:
139
+ """Host only: the URL can come from a secret, and the host is enough to log."""
140
+ return urlparse(endpoint_url).hostname or "unknown"
95
141
 
96
142
  return warmed_companies
97
143
 
@@ -205,8 +251,23 @@ class WarmupService:
205
251
  if provider != "huggingface":
206
252
  return False
207
253
 
254
+ return bool(self._resolve_endpoint_url(company_short_name, inference_tools, tool_name))
255
+
256
+ def _resolve_endpoint_url(
257
+ self,
258
+ company_short_name: str,
259
+ inference_tools: dict,
260
+ tool_name: str,
261
+ ) -> str:
262
+ """Where a tool's requests actually go, or "" when it resolves nowhere.
263
+
264
+ Same precedence the inference client uses — literal, secret ref, then
265
+ environment variable — because this is what identifies the thing being
266
+ woken. Two profiles that resolve here to the same URL are one endpoint,
267
+ whatever they are called in the company's configuration.
268
+ """
208
269
  if not tool_name:
209
- return False
270
+ return ""
210
271
 
211
272
  defaults = inference_tools.get("_defaults") or {}
212
273
  if not isinstance(defaults, dict):
@@ -214,7 +275,7 @@ class WarmupService:
214
275
 
215
276
  tool_cfg = inference_tools.get(tool_name) or {}
216
277
  if not isinstance(tool_cfg, dict):
217
- return False
278
+ return ""
218
279
 
219
280
  resolved_cfg = {**defaults, **tool_cfg}
220
281
  endpoint_url = (resolved_cfg.get("endpoint_url") or "").strip()
@@ -231,4 +292,4 @@ class WarmupService:
231
292
  resolve_secret(self.secret_provider, company_short_name, endpoint_url_env, default="") or ""
232
293
  ).strip()
233
294
 
234
- return bool(endpoint_url)
295
+ return endpoint_url
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 2.41.0
3
+ Version: 2.41.2
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
File without changes
File without changes