iatoolkit 2.61.0__tar.gz → 2.62.0__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 (259) hide show
  1. {iatoolkit-2.61.0/src/iatoolkit.egg-info → iatoolkit-2.62.0}/PKG-INFO +1 -1
  2. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/__init__.py +1 -1
  3. iatoolkit-2.62.0/src/iatoolkit/common/search_terms.py +70 -0
  4. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_tools_pack.yaml +8 -0
  5. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/models.py +15 -1
  6. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/vs_repo.py +112 -6
  7. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/configuration_service.py +4 -0
  8. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/knowledge_base_service.py +30 -1
  9. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/tool_service.py +3 -0
  10. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/rag_api_view.py +11 -1
  11. {iatoolkit-2.61.0 → iatoolkit-2.62.0/src/iatoolkit.egg-info}/PKG-INFO +1 -1
  12. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit.egg-info/SOURCES.txt +1 -0
  13. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/LICENSE +0 -0
  14. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/LICENSE_COMMUNITY.md +0 -0
  15. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/pyproject.toml +0 -0
  16. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/readme.md +0 -0
  17. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/setup.cfg +0 -0
  18. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/base_company.py +0 -0
  19. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/cli_commands.py +0 -0
  20. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/__init__.py +0 -0
  21. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/bridge_validation.py +0 -0
  22. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/company_paths.py +0 -0
  23. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/exceptions.py +0 -0
  24. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/interfaces/__init__.py +0 -0
  25. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
  26. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
  27. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
  28. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
  29. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
  30. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
  31. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/model_registry.py +0 -0
  32. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/routes.py +0 -0
  33. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/secret_resolver.py +0 -0
  34. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/session_manager.py +0 -0
  35. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/text_splitter.py +0 -0
  36. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/url_safety.py +0 -0
  37. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/common/util.py +0 -0
  38. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/company_registry.py +0 -0
  39. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/__init__.py +0 -0
  40. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
  41. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
  42. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
  43. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
  44. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
  45. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
  46. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
  47. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
  48. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
  49. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
  50. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
  51. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
  52. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
  53. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
  54. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
  55. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
  56. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
  57. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
  58. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
  59. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
  60. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
  61. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
  62. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
  63. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
  64. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
  65. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/sql_rules.prompt +0 -0
  66. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
  67. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
  68. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/core.py +0 -0
  69. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/__init__.py +0 -0
  70. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
  71. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/call_service.py +0 -0
  72. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  73. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
  74. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
  75. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
  76. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
  77. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
  78. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
  79. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/google_auth_client.py +0 -0
  80. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
  81. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
  82. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/inference_handler.py +0 -0
  83. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
  84. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
  85. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
  86. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
  87. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
  88. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
  89. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
  90. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
  91. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
  92. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
  93. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/llm_response.py +0 -0
  94. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  95. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/locales/en.yaml +0 -0
  96. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/locales/es.yaml +0 -0
  97. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/__init__.py +0 -0
  98. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/api_key_repo.py +0 -0
  99. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/bridge_repo.py +0 -0
  100. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/database_manager.py +0 -0
  101. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/document_repo.py +0 -0
  102. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
  103. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
  104. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
  105. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
  106. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
  107. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/memory_question_repo.py +0 -0
  108. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/memory_repo.py +0 -0
  109. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
  110. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
  111. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
  112. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
  113. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/runtime_logging.py +0 -0
  114. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/__init__.py +0 -0
  115. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/api_key_service.py +0 -0
  116. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/attachment_policy_service.py +0 -0
  117. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/auth_service.py +0 -0
  118. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/branding_service.py +0 -0
  119. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/company_context_service.py +0 -0
  120. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/context_builder_service.py +0 -0
  121. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
  122. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/embedding_service.py +0 -0
  123. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/excel_service.py +0 -0
  124. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/file_processor_service.py +0 -0
  125. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/history_manager_service.py +0 -0
  126. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/http_tool_service.py +0 -0
  127. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/i18n_service.py +0 -0
  128. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/inference_service.py +0 -0
  129. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/jwt_service.py +0 -0
  130. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/language_service.py +0 -0
  131. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/license_service.py +0 -0
  132. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/llm_client_service.py +0 -0
  133. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/mail_service.py +0 -0
  134. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
  135. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/mcp_token_service.py +0 -0
  136. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/memory_compiler_service.py +0 -0
  137. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
  138. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/memory_question_service.py +0 -0
  139. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/memory_service.py +0 -0
  140. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/memory_wiki_service.py +0 -0
  141. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
  142. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/operator_identity_service.py +0 -0
  143. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/__init__.py +0 -0
  144. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/contracts.py +0 -0
  145. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
  146. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
  147. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
  148. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
  149. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
  150. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
  151. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
  152. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
  153. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/parsers/validator.py +0 -0
  154. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/pdf_service.py +0 -0
  155. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/profile_service.py +0 -0
  156. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/prompt_resource_service.py +0 -0
  157. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/prompt_service.py +0 -0
  158. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/query_service.py +0 -0
  159. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
  160. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/sql_dataset_service.py +0 -0
  161. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/sql_service.py +0 -0
  162. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/sql_source_service.py +0 -0
  163. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/storage_service.py +0 -0
  164. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/structured_output_service.py +0 -0
  165. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
  166. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/system_tools.py +0 -0
  167. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/telemetry_service.py +0 -0
  168. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/tenant_wiki_service.py +0 -0
  169. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/tool_output_contract.py +0 -0
  170. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
  171. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
  172. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/visual_kb_service.py +0 -0
  173. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/visual_tool_service.py +0 -0
  174. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/warmup_service.py +0 -0
  175. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/web_search/__init__.py +0 -0
  176. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
  177. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
  178. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
  179. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/services/web_search_service.py +0 -0
  180. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/images/favicon.ico +0 -0
  181. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/images/favicon.png +0 -0
  182. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
  183. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
  184. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
  185. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
  186. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
  187. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_header_navigation.js +0 -0
  188. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
  189. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
  190. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
  191. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_main.js +0 -0
  192. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
  193. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
  194. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
  195. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
  196. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
  197. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/account.css +0 -0
  198. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
  199. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  200. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
  201. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/documents.css +0 -0
  202. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
  203. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
  204. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
  205. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/_chat_header.html +0 -0
  206. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/_company_header.html +0 -0
  207. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/_login_widget.html +0 -0
  208. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/account.html +0 -0
  209. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/base.html +0 -0
  210. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/change_password.html +0 -0
  211. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/chat.html +0 -0
  212. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
  213. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/chat_modals.html +0 -0
  214. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/error.html +0 -0
  215. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/forgot_password.html +0 -0
  216. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/home_hosted_default.html +0 -0
  217. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/login_global.html +0 -0
  218. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
  219. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
  220. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/pdf/base.html +0 -0
  221. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/pdf/letter.html +0 -0
  222. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/pdf/report.html +0 -0
  223. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/pdf/simple.html +0 -0
  224. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/templates/signup.html +0 -0
  225. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/__init__.py +0 -0
  226. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/account_view.py +0 -0
  227. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/api_key_api_view.py +0 -0
  228. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/base_login_view.py +0 -0
  229. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/categories_api_view.py +0 -0
  230. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/change_password_view.py +0 -0
  231. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/chat_context_preview_api_view.py +0 -0
  232. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/chat_view.py +0 -0
  233. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/configuration_api_view.py +0 -0
  234. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/connectors_api_view.py +0 -0
  235. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/embedding_api_view.py +0 -0
  236. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
  237. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/help_content_api_view.py +0 -0
  238. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/history_api_view.py +0 -0
  239. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/home_view.py +0 -0
  240. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
  241. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
  242. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/load_document_api_view.py +0 -0
  243. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/login_view.py +0 -0
  244. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/logout_api_view.py +0 -0
  245. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/memory_api_view.py +0 -0
  246. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/profile_api_view.py +0 -0
  247. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/prompt_api_view.py +0 -0
  248. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/prompt_context_preview_api_view.py +0 -0
  249. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/prompt_resource_api_view.py +0 -0
  250. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/root_redirect_view.py +0 -0
  251. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/signup_view.py +0 -0
  252. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/static_page_view.py +0 -0
  253. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/tool_api_view.py +0 -0
  254. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
  255. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/users_api_view.py +0 -0
  256. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit/views/verify_user_view.py +0 -0
  257. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  258. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/src/iatoolkit.egg-info/requires.txt +0 -0
  259. {iatoolkit-2.61.0 → iatoolkit-2.62.0}/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.61.0
3
+ Version: 2.62.0
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.61.0"
6
+ __version__ = "2.62.0"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -0,0 +1,70 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ """The words the hybrid search looks for (BL-005, Fase 4).
7
+
8
+ The word branch searches the chunks' full text with Postgres' 'simple'
9
+ configuration, which has no stopwords: a question would match every chunk with
10
+ "de" or "the". So the question is reduced here to the words that mean something,
11
+ and Postgres ORs them (see VSRepo). Exact terms the model passes as `keywords`
12
+ take the place of the question.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+
19
+ # Spanish and English function words. Only what never identifies a document:
20
+ # no domain words, no numbers.
21
+ STOPWORDS = frozenset("""
22
+ a al algo algun alguna algunas alguno algunos ante antes aquel aquella aquello aqui asi aun cada como con
23
+ contra cual cuales cualquier cuando cuanto de del desde donde dos el ella ellas ellos en entre era eran es
24
+ esa esas ese eso esos esta estaba estado estan estar estas este esto estos fue fueron ha hay hace hacer hacia
25
+ han hasta la las le les lo los mas me mi mis mucho muy nada ni no nos nosotros o otra otras otro otros para
26
+ pero poco por porque que quien quienes se sea ser si sido sin sobre son su sus tambien tan tanto te tiene
27
+ tienen todo todos tu tus un una unas uno unos usted ustedes ya yo
28
+ cómo cuál cuáles cuándo cuánto dónde qué quién quiénes también aquí así aún más está están sí tú
29
+ a an and are as at be been but by can could did do does for from had has have how i if in into is it its
30
+ me my no not of on or our should so than that the their them then there these they this those to was we
31
+ were what when where which who why will with would you your
32
+ """.split())
33
+
34
+ # Leading and trailing punctuation that is never part of a term. Inner
35
+ # punctuation is kept: AX-2241, 76.123.456-7, v3.2.
36
+ _EDGE_PUNCTUATION = "¿?¡!.,;:\"'()[]{}«»“”‘’…"
37
+ _MAX_TERMS = 24
38
+ _MAX_TERM_CHARS = 64
39
+
40
+
41
+ def search_terms(query_text: str | None, keywords: str | list | None = None) -> str:
42
+ """
43
+ The text the word branch looks for: the keywords when given, else the query
44
+ without stopwords. Empty when nothing is left, and the caller then searches
45
+ by meaning only.
46
+ """
47
+ if keywords:
48
+ raw = keywords if isinstance(keywords, list) else [keywords]
49
+ source = " ".join(str(item) for item in raw if str(item or "").strip())
50
+ return _clean(source, drop_stopwords=False)
51
+ return _clean(str(query_text or ""), drop_stopwords=True)
52
+
53
+
54
+ def _clean(text: str, *, drop_stopwords: bool) -> str:
55
+ terms: list[str] = []
56
+ seen: set[str] = set()
57
+ for token in re.split(r"\s+", text):
58
+ term = token.strip(_EDGE_PUNCTUATION)
59
+ if not term or not any(char.isalnum() for char in term):
60
+ continue
61
+ if drop_stopwords and term.lower() in STOPWORDS:
62
+ continue
63
+ term = term[:_MAX_TERM_CHARS]
64
+ if term.lower() in seen:
65
+ continue
66
+ seen.add(term.lower())
67
+ terms.append(term)
68
+ if len(terms) >= _MAX_TERMS:
69
+ break
70
+ return " ".join(terms)
@@ -280,6 +280,14 @@ tools:
280
280
  - value
281
281
  additionalProperties: false
282
282
  description: "Opcional en la práctica. Usa null cuando no necesites filtrar por metadata. No inventes filtros ni envíes [] por defecto. Ejemplo: [{'key':'doc.type','value':'invoice'},{'key':'chunk.source_type','value':'table'}]."
283
+ keywords:
284
+ anyOf:
285
+ - type: "null"
286
+ - type: string
287
+ - type: array
288
+ items:
289
+ type: string
290
+ description: "Opcional. Términos exactos que deben aparecer en el texto: códigos, números de contrato, RUT, nombres propios o siglas (ej. [\"AX-2241\", \"Pérez\"]). La búsqueda combina el significado de `query` con estos términos. Usa null si la pregunta no tiene términos exactos."
283
291
  required:
284
292
  - query
285
293
  - function_name: iat_document_list
@@ -8,7 +8,7 @@ import enum
8
8
  from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Enum, Text, JSON, Boolean, ForeignKey, Table, MetaData
9
9
  from sqlalchemy.orm import DeclarativeBase
10
10
  from sqlalchemy.orm import relationship, class_mapper
11
- from sqlalchemy.sql import func
11
+ from sqlalchemy.sql import func, literal_column
12
12
  from sqlalchemy import UniqueConstraint, Index
13
13
  from sqlalchemy.dialects.postgresql import JSONB
14
14
  from datetime import datetime
@@ -690,6 +690,20 @@ class VSDoc(Base):
690
690
  embedding = Column(Vector, nullable=False)
691
691
  company = relationship("Company", back_populates="vsdocs")
692
692
 
693
+ # Full-text index for the hybrid search's word branch (BL-005, Fase 4): an
694
+ # expression index, no extra column, so the table is not rewritten. The
695
+ # 'simple' configuration does not stem, so codes such as AX-2241 or names
696
+ # such as Pérez are indexed as written. Postgres only; existing databases get
697
+ # it from iatoolkit-enterprise/migrations/2026-10-15-vsdocs-text-index.sql,
698
+ # since create_all() never adds an index to a table that already exists.
699
+ __table_args__ = (
700
+ Index(
701
+ "ix_iat_vsdocs_text_simple_tsv",
702
+ func.to_tsvector(literal_column("'simple'::regconfig"), text),
703
+ postgresql_using="gin",
704
+ ).ddl_if(dialect="postgresql"),
705
+ )
706
+
693
707
  def to_dict(self):
694
708
  return {column.key: getattr(self, column.key) for column in class_mapper(self.__class__).columns}
695
709
 
@@ -14,6 +14,7 @@ from sqlalchemy import text
14
14
  from injector import inject
15
15
 
16
16
  from iatoolkit.common.exceptions import IAToolkitException
17
+ from iatoolkit.common.search_terms import search_terms
17
18
  from iatoolkit.repositories.database_manager import DatabaseManager
18
19
  from iatoolkit.repositories.models import Company, VSDoc, VSImage
19
20
  from iatoolkit.services.embedding_service import EmbeddingService
@@ -96,6 +97,14 @@ class VSRepo:
96
97
  query = query.limit(max(int(limit), 0))
97
98
  return list(query.all())
98
99
 
100
+ SEARCH_MODE_VECTOR = "vector"
101
+ SEARCH_MODE_HYBRID = "hybrid"
102
+ SEARCH_MODES = (SEARCH_MODE_VECTOR, SEARCH_MODE_HYBRID)
103
+ # Each branch of the hybrid search brings this many candidates to the fusion.
104
+ HYBRID_CANDIDATES = 40
105
+ # Reciprocal Rank Fusion constant: a chunk adds 1 / (RRF_K + rank) per list.
106
+ RRF_K = 60
107
+
99
108
  def query(self,
100
109
  company_short_name: str,
101
110
  query_text: str,
@@ -103,6 +112,8 @@ class VSRepo:
103
112
  metadata_filter=None,
104
113
  collection_ids: List[int] | None = None,
105
114
  include_urls: bool = True,
115
+ search_mode: str = SEARCH_MODE_VECTOR,
116
+ keywords: str | list | None = None,
106
117
  ) -> list[Dict]:
107
118
  """
108
119
  search documents similar to the query for a company
@@ -113,6 +124,10 @@ class VSRepo:
113
124
  n_results: max number of results to return
114
125
  metadata_filter: (e.g., {"document_type": "certificate"})
115
126
  include_urls: Generate storage URLs for returned chunks when true.
127
+ search_mode: "vector" (by meaning) or "hybrid" (by meaning and by
128
+ words, fused by rank; BL-005, Fase 4).
129
+ keywords: exact terms for the hybrid search's word branch, instead
130
+ of the query's own words.
116
131
 
117
132
  Returns:
118
133
  list of documents matching the query and filters
@@ -214,11 +229,18 @@ class VSRepo:
214
229
  sql_query_parts.extend(metadata_sql)
215
230
  params.update(metadata_params)
216
231
 
217
- # join all the query parts
218
- sql_query = "".join(sql_query_parts)
232
+ text_terms = search_terms(query_text, keywords) if search_mode == self.SEARCH_MODE_HYBRID else ""
233
+ hybrid = bool(text_terms)
234
+ if hybrid:
235
+ params["text_terms"] = text_terms
236
+ params["candidates"] = max(int(n_results or 5), self.HYBRID_CANDIDATES)
237
+ sql_query = self._hybrid_sql(sql_query_parts, distance_expression)
238
+ else:
239
+ # join all the query parts
240
+ sql_query = "".join(sql_query_parts)
219
241
 
220
- # add sorting and limit of results
221
- sql_query += " ORDER BY distance ASC LIMIT :n_results"
242
+ # add sorting and limit of results
243
+ sql_query += " ORDER BY distance ASC LIMIT :n_results"
222
244
 
223
245
  logging.debug(f"Executing SQL query: {sql_query}")
224
246
  logging.debug(f"With parameters: {params}")
@@ -231,6 +253,7 @@ class VSRepo:
231
253
  rows = result.fetchall()
232
254
  vector_sql_duration_ms = round((perf_counter() - vector_sql_started_at) * 1000)
233
255
  vs_documents = []
256
+ matches = {"both": 0, "vector": 0, "text": 0}
234
257
 
235
258
  for row in rows:
236
259
  # create the document object with the data
@@ -238,6 +261,12 @@ class VSRepo:
238
261
  chunk_meta = row[6] if len(row) > 6 and row[6] is not None else {}
239
262
 
240
263
  distance = float(row[7]) if len(row) > 7 and row[7] is not None else None
264
+ hybrid_fields = {}
265
+ if hybrid:
266
+ in_vector, in_text = bool(row[9]), bool(row[10])
267
+ match = "both" if in_vector and in_text else "vector" if in_vector else "text"
268
+ matches[match] += 1
269
+ hybrid_fields = {"match": match, "rrf_score": float(row[8]) if row[8] is not None else None}
241
270
 
242
271
  # get the url of the document
243
272
  storage_key = row[3] if len(row) > 3 and row[3] is not None else None
@@ -257,17 +286,21 @@ class VSRepo:
257
286
  'distance': distance,
258
287
  'distance_metric': distance_metric,
259
288
  'score': self._score_from_l2_distance(distance),
289
+ 'search_mode': self.SEARCH_MODE_HYBRID if hybrid else self.SEARCH_MODE_VECTOR,
290
+ **hybrid_fields,
260
291
  }
261
292
  )
262
293
 
263
294
  logging.info(
264
- "RAG vector query completed: company=%s collection_ids=%s dimensions=%s n_results=%s rows=%s "
265
- "embedding_ms=%s company_lookup_ms=%s vector_sql_ms=%s total_ms=%s",
295
+ "RAG %s query completed: company=%s collection_ids=%s dimensions=%s n_results=%s rows=%s "
296
+ "matches=%s embedding_ms=%s company_lookup_ms=%s vector_sql_ms=%s total_ms=%s",
297
+ self.SEARCH_MODE_HYBRID if hybrid else self.SEARCH_MODE_VECTOR,
266
298
  company_short_name,
267
299
  normalized_collection_ids,
268
300
  embedding_dimensions,
269
301
  n_results,
270
302
  len(rows),
303
+ matches if hybrid else None,
271
304
  embedding_duration_ms,
272
305
  company_lookup_duration_ms,
273
306
  vector_sql_duration_ms,
@@ -288,6 +321,79 @@ class VSRepo:
288
321
  finally:
289
322
  self.session.close()
290
323
 
324
+ def _hybrid_sql(self, sql_query_parts: list[str], distance_expression: str) -> str:
325
+ """
326
+ Hybrid search in one statement: the nearest chunks by meaning and the
327
+ best chunks by words (Postgres full text, 'simple' configuration, the
328
+ terms OR'ed), each ranked, fused by Reciprocal Rank Fusion.
329
+
330
+ `sql_query_parts` is the vector query with every filter (company,
331
+ dimension, collections, metadata); both branches reuse its WHERE, so they
332
+ see exactly the same chunks. The word branch has its own expression, the
333
+ one the GIN index ix_iat_vsdocs_text_simple_tsv is built on.
334
+ """
335
+ base = "".join(sql_query_parts)
336
+ _, where = base.split("WHERE", 1)
337
+ # The dimension condition is for the vector index; the word branch does
338
+ # not need it (a chunk is a chunk whatever its embedding size).
339
+ text_where = re.sub(r"\s+AND vector_dims\(iat_vsdocs\.embedding\) = \d+", "", where)
340
+ tsvector = "to_tsvector('simple'::regconfig, iat_vsdocs.text)"
341
+ # plainto_tsquery parses the terms exactly as the index parsed the text
342
+ # (and cannot be broken by any character); its ANDs become ORs.
343
+ tsquery = "replace(plainto_tsquery('simple'::regconfig, :text_terms)::text, ' & ', ' | ')::tsquery"
344
+ return f"""
345
+ WITH vector_hits AS (
346
+ SELECT id, distance, ROW_NUMBER() OVER (ORDER BY distance ASC) AS rank
347
+ FROM (
348
+ SELECT iat_vsdocs.id, ({distance_expression}) AS distance
349
+ FROM iat_vsdocs, iat_documents
350
+ WHERE {where}
351
+ ORDER BY distance ASC
352
+ LIMIT :candidates
353
+ ) nearest
354
+ ),
355
+ text_hits AS (
356
+ SELECT id, ROW_NUMBER() OVER (ORDER BY text_rank DESC, id ASC) AS rank
357
+ FROM (
358
+ SELECT iat_vsdocs.id, ts_rank_cd({tsvector}, terms.q) AS text_rank
359
+ FROM iat_vsdocs, iat_documents, (SELECT {tsquery} AS q) terms
360
+ WHERE {text_where}
361
+ AND {tsvector} @@ terms.q
362
+ ORDER BY text_rank DESC
363
+ LIMIT :candidates
364
+ ) best
365
+ ),
366
+ fused AS (
367
+ SELECT id,
368
+ SUM(1.0 / ({self.RRF_K} + rank)) AS rrf_score,
369
+ MIN(distance) AS distance,
370
+ BOOL_OR(source = 'vector') AS in_vector,
371
+ BOOL_OR(source = 'text') AS in_text
372
+ FROM (
373
+ SELECT id, rank, distance, 'vector' AS source FROM vector_hits
374
+ UNION ALL
375
+ SELECT id, rank, NULL::float AS distance, 'text' AS source FROM text_hits
376
+ ) both_lists
377
+ GROUP BY id
378
+ )
379
+ SELECT iat_vsdocs.id,
380
+ iat_documents.filename,
381
+ iat_vsdocs.text,
382
+ iat_documents.storage_key,
383
+ iat_documents.meta,
384
+ iat_documents.id,
385
+ iat_vsdocs.meta,
386
+ fused.distance,
387
+ fused.rrf_score,
388
+ fused.in_vector,
389
+ fused.in_text
390
+ FROM fused
391
+ JOIN iat_vsdocs ON iat_vsdocs.id = fused.id
392
+ JOIN iat_documents ON iat_documents.id = iat_vsdocs.document_id
393
+ ORDER BY fused.rrf_score DESC, fused.distance ASC NULLS LAST
394
+ LIMIT :n_results
395
+ """
396
+
291
397
  def query_images(self,
292
398
  company_short_name: str,
293
399
  query_text: str,
@@ -1059,6 +1059,10 @@ class ConfigurationService:
1059
1059
  if kb_config and not isinstance(kb_config, dict):
1060
1060
  add_error("knowledge_base", "Section must be a dictionary.")
1061
1061
  elif kb_config:
1062
+ search_mode = kb_config.get("search_mode")
1063
+ if search_mode is not None and str(search_mode).strip().lower() not in {"vector", "hybrid"}:
1064
+ add_error("knowledge_base.search_mode", "Must be 'vector' or 'hybrid'.")
1065
+
1062
1066
  parsing_provider = kb_config.get("parsing_provider")
1063
1067
  allowed_parsing_providers = {"auto", "docling", "basic"}
1064
1068
  if parsing_provider is not None:
@@ -491,9 +491,15 @@ class KnowledgeBaseService:
491
491
  collection: str | list[str] | None = None,
492
492
  metadata_filter: dict = None,
493
493
  include_urls: bool = True,
494
+ keywords: str | list | None = None,
495
+ search_mode: str | None = None,
494
496
  ):
495
497
  """
496
- Performs a semantic search and returns the list of Document objects (chunks).
498
+ Searches the company's chunks and returns them (as dicts).
499
+
500
+ By meaning and by words, fused, unless the company sets
501
+ `knowledge_base.search_mode: vector` (BL-005, Fase 4). `keywords` are
502
+ exact terms for the word part; `search_mode` overrides the company's.
497
503
  Useful for UI displays where structured data is needed instead of a raw string context.
498
504
 
499
505
  Args:
@@ -536,10 +542,33 @@ class KnowledgeBaseService:
536
542
  metadata_filter=metadata_filter,
537
543
  collection_ids=collection_ids,
538
544
  include_urls=include_urls,
545
+ search_mode=self.resolve_search_mode(company_short_name, search_mode),
546
+ keywords=keywords,
539
547
  )
540
548
 
541
549
  return chunk_list
542
550
 
551
+ DEFAULT_SEARCH_MODE = "hybrid"
552
+ SEARCH_MODES = ("vector", "hybrid")
553
+
554
+ def resolve_search_mode(self, company_short_name: str, requested: str | None = None) -> str:
555
+ """The requested mode, else company.yaml's knowledge_base.search_mode, else hybrid."""
556
+ candidate = str(requested or "").strip().lower()
557
+ if candidate in self.SEARCH_MODES:
558
+ return candidate
559
+ try:
560
+ from iatoolkit import current_iatoolkit
561
+ from iatoolkit.services.configuration_service import ConfigurationService
562
+
563
+ # Resolved here, not injected: ConfigurationService builds this service
564
+ # to sync the collections when a company's config loads.
565
+ kb_config = current_iatoolkit().get_injector().get(ConfigurationService).get_configuration(
566
+ company_short_name, "knowledge_base") or {}
567
+ except Exception:
568
+ kb_config = {}
569
+ configured = str((kb_config or {}).get("search_mode") or "").strip().lower() if isinstance(kb_config, dict) else ""
570
+ return configured if configured in self.SEARCH_MODES else self.DEFAULT_SEARCH_MODE
571
+
543
572
  @staticmethod
544
573
  def _normalize_search_collection(collection: str | list[str] | None) -> str | list[str] | None:
545
574
  if collection is None:
@@ -185,13 +185,16 @@ class ToolService:
185
185
  collection: str | list[str] | None = None,
186
186
  metadata_filter=None,
187
187
  n_results: int = 5,
188
+ keywords=None,
188
189
  **kwargs):
190
+ search_kwargs = {"keywords": keywords} if keywords else {}
189
191
  chunks = self.knowledge_base_service.search(
190
192
  company_short_name=company_short_name,
191
193
  query=query,
192
194
  n_results=n_results,
193
195
  collection=collection,
194
196
  metadata_filter=metadata_filter,
197
+ **search_kwargs,
195
198
  )
196
199
  typed_chunks = self._normalize_chunks_for_tool(chunks)
197
200
 
@@ -310,23 +310,33 @@ class RagApiView(MethodView):
310
310
  n_results = int(data.get('k', 5))
311
311
  collection = data.get('collection')
312
312
  metadata_filter = data.get('metadata_filter')
313
+ # The Search Lab can compare modes; empty means the company's own.
314
+ search_mode = str(data.get('search_mode') or '').strip().lower() or None
315
+ keywords = data.get('keywords') or None
313
316
 
314
317
  if not query:
315
318
  msg = self.i18n_service.t('rag.search.query_required')
316
319
  return jsonify({'result': 'error', 'message': msg}), 400
317
320
 
318
321
  # 3. Call Service
322
+ extra = {}
323
+ if search_mode:
324
+ extra['search_mode'] = search_mode
325
+ if keywords:
326
+ extra['keywords'] = keywords
319
327
  chunks = self.knowledge_base_service.search(
320
328
  company_short_name=company_short_name,
321
329
  query=query,
322
330
  n_results=n_results,
323
331
  collection=collection,
324
332
  metadata_filter=metadata_filter,
333
+ **extra,
325
334
  )
326
335
 
327
336
  return jsonify({
328
337
  "result": "success",
329
- "chunks": chunks
338
+ "chunks": chunks,
339
+ "search_mode": chunks[0].get("search_mode") if chunks else None,
330
340
  }), 200
331
341
 
332
342
  except IAToolkitException as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 2.61.0
3
+ Version: 2.62.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -19,6 +19,7 @@ src/iatoolkit/common/company_paths.py
19
19
  src/iatoolkit/common/exceptions.py
20
20
  src/iatoolkit/common/model_registry.py
21
21
  src/iatoolkit/common/routes.py
22
+ src/iatoolkit/common/search_terms.py
22
23
  src/iatoolkit/common/secret_resolver.py
23
24
  src/iatoolkit/common/session_manager.py
24
25
  src/iatoolkit/common/text_splitter.py
File without changes
File without changes
File without changes
File without changes