iatoolkit 2.76.10__tar.gz → 2.77.1__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 (285) hide show
  1. {iatoolkit-2.76.10/src/iatoolkit.egg-info → iatoolkit-2.77.1}/PKG-INFO +1 -1
  2. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/__init__.py +1 -1
  3. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/access.py +10 -3
  4. iatoolkit-2.77.1/src/iatoolkit/common/decision_types.py +376 -0
  5. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_proxy.py +19 -0
  6. iatoolkit-2.77.1/src/iatoolkit/infra/openrouter_decision_client.py +177 -0
  7. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/llm_query_repo.py +1 -0
  8. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/models.py +12 -0
  9. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/configuration_service.py +14 -0
  10. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/context_builder_service.py +42 -14
  11. iatoolkit-2.77.1/src/iatoolkit/services/decision_service.py +190 -0
  12. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/llm_client_service.py +141 -0
  13. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/prompt_service.py +114 -1
  14. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/query_service.py +85 -0
  15. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_agent_runner.py +4 -2
  16. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/account_view.py +3 -0
  17. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/prompt_api_view.py +8 -0
  18. {iatoolkit-2.76.10 → iatoolkit-2.77.1/src/iatoolkit.egg-info}/PKG-INFO +1 -1
  19. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit.egg-info/SOURCES.txt +3 -0
  20. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/LICENSE +0 -0
  21. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/LICENSE_COMMUNITY.md +0 -0
  22. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/pyproject.toml +0 -0
  23. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/readme.md +0 -0
  24. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/setup.cfg +0 -0
  25. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/base_company.py +0 -0
  26. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/cli_commands.py +0 -0
  27. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/__init__.py +0 -0
  28. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/bridge_validation.py +0 -0
  29. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/company_paths.py +0 -0
  30. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/exceptions.py +0 -0
  31. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/__init__.py +0 -0
  32. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
  33. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
  34. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/history_compaction_trigger.py +0 -0
  35. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
  36. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
  37. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
  38. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
  39. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/memory_tools.py +0 -0
  40. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/model_registry.py +0 -0
  41. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/routes.py +0 -0
  42. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/search_terms.py +0 -0
  43. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/secret_resolver.py +0 -0
  44. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/session_manager.py +0 -0
  45. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/text_splitter.py +0 -0
  46. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/url_safety.py +0 -0
  47. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/util.py +0 -0
  48. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/company_registry.py +0 -0
  49. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/__init__.py +0 -0
  50. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
  51. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/history_compaction.prompt +0 -0
  52. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_compiler.prompt +0 -0
  53. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_correction.prompt +0 -0
  54. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_document_ingest.prompt +0 -0
  55. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_image_ingest.prompt +0 -0
  56. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_meeting_ingest.prompt +0 -0
  57. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_question_review.prompt +0 -0
  58. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents_pack.yaml +0 -0
  59. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
  60. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
  61. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
  62. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
  63. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
  64. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
  65. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
  66. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
  67. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
  68. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
  69. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
  70. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
  71. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
  72. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
  73. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
  74. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
  75. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
  76. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
  77. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
  78. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
  79. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
  80. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
  81. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
  82. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
  83. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
  84. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
  85. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_tools_pack.yaml +0 -0
  86. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/core.py +0 -0
  87. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/__init__.py +0 -0
  88. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
  89. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/call_service.py +0 -0
  90. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  91. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
  92. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
  93. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
  94. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
  95. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
  96. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
  97. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/google_auth_client.py +0 -0
  98. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/google_chat_app.py +0 -0
  99. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
  100. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/inference_handler.py +0 -0
  101. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
  102. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
  103. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
  104. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
  105. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
  106. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
  107. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
  108. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
  109. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
  110. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_response.py +0 -0
  111. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  112. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/locales/en.yaml +0 -0
  113. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/locales/es.yaml +0 -0
  114. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/__init__.py +0 -0
  115. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/advisory_lock.py +0 -0
  116. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/api_key_repo.py +0 -0
  117. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/bridge_repo.py +0 -0
  118. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/database_manager.py +0 -0
  119. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/document_repo.py +0 -0
  120. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
  121. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
  122. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
  123. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
  124. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/memory_question_repo.py +0 -0
  125. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/memory_repo.py +0 -0
  126. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/memory_usage_repo.py +0 -0
  127. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/profile_repo.py +0 -0
  128. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
  129. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
  130. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
  131. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/vs_repo.py +0 -0
  132. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/runtime_logging.py +0 -0
  133. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/__init__.py +0 -0
  134. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/api_key_service.py +0 -0
  135. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/attachment_policy_service.py +0 -0
  136. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/auth_service.py +0 -0
  137. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/branding_service.py +0 -0
  138. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/company_context_service.py +0 -0
  139. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/dispatcher_service.py +0 -0
  140. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/embedding_service.py +0 -0
  141. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/excel_service.py +0 -0
  142. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/file_processor_service.py +0 -0
  143. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/history_compaction_service.py +0 -0
  144. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/history_manager_service.py +0 -0
  145. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/http_tool_service.py +0 -0
  146. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/i18n_service.py +0 -0
  147. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/inference_service.py +0 -0
  148. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/jwt_service.py +0 -0
  149. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/knowledge_base_service.py +0 -0
  150. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/language_service.py +0 -0
  151. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/license_service.py +0 -0
  152. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/mail_service.py +0 -0
  153. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
  154. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/mcp_token_service.py +0 -0
  155. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_compiler_service.py +0 -0
  156. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
  157. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_question_service.py +0 -0
  158. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_service.py +0 -0
  159. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_sources.py +0 -0
  160. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_usage_service.py +0 -0
  161. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_wiki_service.py +0 -0
  162. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/noop_history_compaction_trigger.py +0 -0
  163. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
  164. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/operator_identity_service.py +0 -0
  165. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/__init__.py +0 -0
  166. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/contracts.py +0 -0
  167. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
  168. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
  169. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
  170. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
  171. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
  172. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
  173. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
  174. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
  175. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/validator.py +0 -0
  176. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/pdf_service.py +0 -0
  177. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/profile_service.py +0 -0
  178. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/prompt_resource_service.py +0 -0
  179. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/resource_access_scope.py +0 -0
  180. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/signup_guard_service.py +0 -0
  181. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
  182. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_catalog_service.py +0 -0
  183. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_dataset_service.py +0 -0
  184. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_service.py +0 -0
  185. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_source_service.py +0 -0
  186. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/storage_service.py +0 -0
  187. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/structured_output_service.py +0 -0
  188. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_agent_catalog.py +0 -0
  189. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
  190. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_tools.py +0 -0
  191. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/telemetry_service.py +0 -0
  192. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/tenant_wiki_service.py +0 -0
  193. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/tool_output_contract.py +0 -0
  194. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/tool_service.py +0 -0
  195. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/user_feedback_service.py +0 -0
  196. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/user_session_context_service.py +0 -0
  197. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/visual_kb_service.py +0 -0
  198. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/visual_tool_service.py +0 -0
  199. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/warmup_service.py +0 -0
  200. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/__init__.py +0 -0
  201. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
  202. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
  203. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
  204. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search_service.py +0 -0
  205. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/wiki_page_index_service.py +0 -0
  206. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/favicon.ico +0 -0
  207. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/favicon.png +0 -0
  208. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/fernando.jpeg +0 -0
  209. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
  210. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
  211. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
  212. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_filepond.js +0 -0
  213. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_header_navigation.js +0 -0
  214. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_help_content.js +0 -0
  215. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_history_button.js +0 -0
  216. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
  217. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_main.js +0 -0
  218. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
  219. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
  220. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
  221. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
  222. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/account.css +0 -0
  223. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
  224. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  225. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/chat_public.css +0 -0
  226. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/documents.css +0 -0
  227. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/landing_page.css +0 -0
  228. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/llm_output.css +0 -0
  229. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/onboarding.css +0 -0
  230. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/wiki_page.css +0 -0
  231. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/_chat_header.html +0 -0
  232. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/_company_header.html +0 -0
  233. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/_login_widget.html +0 -0
  234. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/account.html +0 -0
  235. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/base.html +0 -0
  236. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/change_password.html +0 -0
  237. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/chat.html +0 -0
  238. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
  239. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/chat_modals.html +0 -0
  240. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/error.html +0 -0
  241. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/forgot_password.html +0 -0
  242. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/home_hosted_default.html +0 -0
  243. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/login_global.html +0 -0
  244. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
  245. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/base.html +0 -0
  246. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/letter.html +0 -0
  247. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/report.html +0 -0
  248. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/simple.html +0 -0
  249. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/signup.html +0 -0
  250. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/wiki_page.html +0 -0
  251. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/__init__.py +0 -0
  252. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/api_key_api_view.py +0 -0
  253. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/base_login_view.py +0 -0
  254. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/categories_api_view.py +0 -0
  255. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/change_password_view.py +0 -0
  256. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/chat_context_preview_api_view.py +0 -0
  257. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/chat_view.py +0 -0
  258. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/configuration_api_view.py +0 -0
  259. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/connectors_api_view.py +0 -0
  260. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/embedding_api_view.py +0 -0
  261. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/forgot_password_view.py +0 -0
  262. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/help_content_api_view.py +0 -0
  263. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/history_api_view.py +0 -0
  264. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/home_view.py +0 -0
  265. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/init_context_api_view.py +0 -0
  266. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/llmquery_api_view.py +0 -0
  267. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/load_document_api_view.py +0 -0
  268. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/login_view.py +0 -0
  269. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/logout_api_view.py +0 -0
  270. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/memory_api_view.py +0 -0
  271. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/profile_api_view.py +0 -0
  272. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/prompt_context_preview_api_view.py +0 -0
  273. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/prompt_resource_api_view.py +0 -0
  274. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/rag_api_view.py +0 -0
  275. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/root_redirect_view.py +0 -0
  276. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/signup_view.py +0 -0
  277. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/static_page_view.py +0 -0
  278. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/tool_api_view.py +0 -0
  279. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
  280. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/users_api_view.py +0 -0
  281. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/verify_user_view.py +0 -0
  282. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/wiki_page_view.py +0 -0
  283. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  284. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit.egg-info/requires.txt +0 -0
  285. {iatoolkit-2.76.10 → iatoolkit-2.77.1}/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.76.10
3
+ Version: 2.77.1
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.76.10"
6
+ __version__ = "2.77.1"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -29,6 +29,8 @@ from __future__ import annotations
29
29
  import logging
30
30
  from typing import Callable, Protocol
31
31
 
32
+ from iatoolkit.common.decision_types import chat_models, is_decision_model
33
+
32
34
  USE = 1
33
35
  EDIT = 2
34
36
  ADMIN = 3
@@ -181,11 +183,16 @@ def filter_llm_models(company_short_name: str, user_identifier: str | None, defa
181
183
  """The chat's model choice limited to the models this person may use (BL-010, Fase 2).
182
184
 
183
185
  When the company's default is not among them, the first one they may use becomes
184
- their default."""
186
+ their default. Decision models (BL-008) never reach the chat: they answer
187
+ questions about a case, not messages."""
188
+ decision_ids = {str(m.get("id") or "") for m in models or [] if isinstance(m, dict) and is_decision_model(m)}
189
+ models = chat_models(models)
190
+ if default_model in decision_ids:
191
+ default_model = str((models[0] or {}).get("id") or "") or None if models else None
185
192
  if not user_identifier or not Access.registered():
186
193
  return default_model, models
187
- allowed = [m for m in models or [] if can(company_short_name, user_identifier, USE, "model",
188
- str((m or {}).get("id") or ""))]
194
+ allowed = [m for m in models if can(company_short_name, user_identifier, USE, "model",
195
+ str((m or {}).get("id") or ""))]
189
196
  allowed_ids = [str(m.get("id")) for m in allowed]
190
197
  if default_model and default_model not in allowed_ids:
191
198
  default_model = allowed_ids[0] if allowed_ids else None
@@ -0,0 +1,376 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ """Types of a decision model call (BL-008).
7
+
8
+ A decision model does not write text: it answers a fixed set of questions about
9
+ a case (the `state`) with calibrated probabilities. The contract is the one
10
+ OpenRouter serves at `/api/alpha/decisions` for every decision model, and it is
11
+ copied here as is, without a translation layer: questions travel as a map
12
+ `{name: question}`, and each answer comes back under the same name.
13
+
14
+ Only three question types are supported: `noul` (yes/no), `choice` (one of a set
15
+ of options) and `score` (an ordered scale). The API also has `box`, a bounding
16
+ box over an image, which needs `assets` and is out of the first stage.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ from dataclasses import dataclass, field
23
+ from typing import Any
24
+
25
+ QUESTION_NOUL = "noul"
26
+ QUESTION_CHOICE = "choice"
27
+ QUESTION_SCORE = "score"
28
+ QUESTION_TYPES = (QUESTION_NOUL, QUESTION_CHOICE, QUESTION_SCORE)
29
+
30
+ # Same shape as a tool argument name: the answers come back keyed by it, and the
31
+ # agent's structured output exposes them under it.
32
+ _QUESTION_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,63}$")
33
+ MAX_QUESTIONS = 20
34
+
35
+
36
+ DECISION_MODEL_KIND = "decision"
37
+
38
+
39
+ def model_kind(model_config: dict | None) -> str:
40
+ """The kind of model a configuration describes: "decision", or "" for chat.
41
+
42
+ Comes from the catalogue (`capabilities.kind`, written by `models-sync`); a
43
+ company.yaml entry outside the catalogue may state it at the top level.
44
+ """
45
+ if not isinstance(model_config, dict):
46
+ return ""
47
+ capabilities = model_config.get("capabilities")
48
+ kind = capabilities.get("kind") if isinstance(capabilities, dict) else None
49
+ return str(kind or model_config.get("kind") or "").strip().lower()
50
+
51
+
52
+ def is_decision_model(model_config: dict | None) -> bool:
53
+ return model_kind(model_config) == DECISION_MODEL_KIND
54
+
55
+
56
+ def chat_models(models: list | None) -> list:
57
+ """The models that write text: everything but decision models.
58
+
59
+ A company enables decision models like any other, so they are in its list
60
+ of models; but they cannot hold a conversation, so every place that picks a
61
+ model to chat with (the chat selector, the default, system agents) skips them.
62
+ """
63
+ return [model for model in models or [] if not (isinstance(model, dict) and is_decision_model(model))]
64
+
65
+
66
+ class DecisionSpecError(ValueError):
67
+ """A decision spec that cannot be sent: the message names the field."""
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class DecisionQuestion:
72
+ name: str
73
+ type: str
74
+ instructions: str
75
+ #: `noul`: optional {"true": ..., "false": ...}; `choice`: {option: description};
76
+ #: `score`: list of level descriptions, index 0 first.
77
+ criteria: dict[str, str] | list[str] | None = None
78
+
79
+ def to_wire(self) -> dict[str, Any]:
80
+ payload: dict[str, Any] = {"type": self.type, "instructions": self.instructions}
81
+ if self.criteria:
82
+ payload["criteria"] = self.criteria
83
+ return payload
84
+
85
+
86
+ def _clean_text(value: Any) -> str:
87
+ return str(value if value is not None else "").strip()
88
+
89
+
90
+ def parse_question(name: str, raw: Any) -> DecisionQuestion:
91
+ if not isinstance(name, str) or not _QUESTION_NAME_RE.match(name):
92
+ raise DecisionSpecError(
93
+ f"Question name '{name}' is invalid: use letters, digits and '_', starting with a letter."
94
+ )
95
+ if not isinstance(raw, dict):
96
+ raise DecisionSpecError(f"Question '{name}' must be an object.")
97
+
98
+ question_type = _clean_text(raw.get("type")).lower()
99
+ if question_type not in QUESTION_TYPES:
100
+ raise DecisionSpecError(
101
+ f"Question '{name}' has type '{question_type}'; expected one of {', '.join(QUESTION_TYPES)}."
102
+ )
103
+
104
+ instructions = _clean_text(raw.get("instructions"))
105
+ if not instructions:
106
+ raise DecisionSpecError(f"Question '{name}' needs 'instructions'.")
107
+
108
+ raw_criteria = raw.get("criteria")
109
+ criteria: dict[str, str] | list[str] | None = None
110
+
111
+ if question_type == QUESTION_NOUL:
112
+ if raw_criteria not in (None, {}, ""):
113
+ if not isinstance(raw_criteria, dict):
114
+ raise DecisionSpecError(f"Question '{name}': 'criteria' of a noul is {{true, false}}.")
115
+ unknown = set(str(key).lower() for key in raw_criteria) - {"true", "false"}
116
+ if unknown:
117
+ raise DecisionSpecError(
118
+ f"Question '{name}': noul criteria only accept 'true' and 'false'."
119
+ )
120
+ criteria = {
121
+ str(key).lower(): _clean_text(value)
122
+ for key, value in raw_criteria.items()
123
+ if _clean_text(value)
124
+ } or None
125
+
126
+ elif question_type == QUESTION_CHOICE:
127
+ if not isinstance(raw_criteria, dict) or len(raw_criteria) < 2:
128
+ raise DecisionSpecError(
129
+ f"Question '{name}': a choice needs 'criteria' with at least two options."
130
+ )
131
+ criteria = {}
132
+ for option, description in raw_criteria.items():
133
+ option_name = _clean_text(option)
134
+ option_description = _clean_text(description)
135
+ if not option_name:
136
+ raise DecisionSpecError(f"Question '{name}': an option has no name.")
137
+ if not option_description:
138
+ raise DecisionSpecError(f"Question '{name}': option '{option_name}' needs a description.")
139
+ criteria[option_name] = option_description
140
+
141
+ else: # score
142
+ if not isinstance(raw_criteria, list) or len(raw_criteria) < 2:
143
+ raise DecisionSpecError(
144
+ f"Question '{name}': a score needs 'criteria' as a list of at least two levels."
145
+ )
146
+ criteria = [_clean_text(level) for level in raw_criteria]
147
+ if any(not level for level in criteria):
148
+ raise DecisionSpecError(f"Question '{name}': every score level needs a description.")
149
+
150
+ return DecisionQuestion(name=name, type=question_type, instructions=instructions, criteria=criteria)
151
+
152
+
153
+ def parse_decision_spec(spec: Any) -> dict[str, DecisionQuestion]:
154
+ """The questions of a decision spec, validated; raises DecisionSpecError.
155
+
156
+ The spec is `{"questions": {name: question}}`. It is validated in code before
157
+ any call because the API answers a bad question with a 400 that only names
158
+ the field path, after a network round trip.
159
+ """
160
+ if not isinstance(spec, dict):
161
+ raise DecisionSpecError("The decision spec must be an object with 'questions'.")
162
+ raw_questions = spec.get("questions")
163
+ if not isinstance(raw_questions, dict) or not raw_questions:
164
+ raise DecisionSpecError("The decision spec needs at least one question.")
165
+ if len(raw_questions) > MAX_QUESTIONS:
166
+ raise DecisionSpecError(f"A decision spec accepts at most {MAX_QUESTIONS} questions.")
167
+ return {name: parse_question(name, raw) for name, raw in raw_questions.items()}
168
+
169
+
170
+ def normalize_decision_spec(spec: Any, *, allow_empty: bool = False) -> dict[str, Any]:
171
+ """The spec as it is stored: validated questions, in their order.
172
+
173
+ `allow_empty` is for saving: a decision agent exists from the moment it is
174
+ created, before anyone has written its questions. Running one still needs
175
+ at least one (`parse_decision_spec`).
176
+ """
177
+ if allow_empty and isinstance(spec, dict) and not spec.get("questions"):
178
+ return {"questions": {}}
179
+ questions = parse_decision_spec(spec)
180
+ return {"questions": {name: question.to_wire() for name, question in questions.items()}}
181
+
182
+
183
+ def decision_output_schema(spec: Any) -> dict[str, Any]:
184
+ """The JSON Schema of a decision agent's answers, derived from its questions.
185
+
186
+ Describes `DecisionResult.answers_dict()`: one property per question, whose
187
+ `value` is the answer itself (a boolean for noul, one of the options for
188
+ choice, the 0-based weighted average for score). Published as the agent's
189
+ MCP `outputSchema`, so a client knows the shape before calling. Questions
190
+ that do not validate are left out rather than failing the whole catalogue.
191
+ """
192
+ try:
193
+ questions = parse_decision_spec(spec)
194
+ except DecisionSpecError:
195
+ questions = {}
196
+ probability = {"type": "number", "minimum": 0, "maximum": 1}
197
+ properties: dict[str, Any] = {}
198
+ for name, question in questions.items():
199
+ if question.type == QUESTION_NOUL:
200
+ value = {"type": ["boolean", "null"], "description": "true = sí, false = no."}
201
+ probabilities = {
202
+ "type": "object",
203
+ "properties": {"true": probability, "false": probability},
204
+ }
205
+ extra: dict[str, Any] = {}
206
+ elif question.type == QUESTION_CHOICE:
207
+ options = list(question.criteria or {})
208
+ value = {"type": ["string", "null"], "enum": options + [None]}
209
+ probabilities = {
210
+ "type": "object",
211
+ "properties": {option: probability for option in options},
212
+ }
213
+ extra = {}
214
+ else:
215
+ levels = list(question.criteria or [])
216
+ value = {
217
+ "type": ["number", "null"],
218
+ "minimum": 0,
219
+ "maximum": max(len(levels) - 1, 0),
220
+ "description": "Weighted average of the levels; 0 is the first one.",
221
+ }
222
+ probabilities = {
223
+ "type": "object",
224
+ "properties": {str(index): probability for index in range(len(levels))},
225
+ }
226
+ extra = {"legend": {"type": "object", "additionalProperties": {"type": "string"}}}
227
+ properties[name] = {
228
+ "type": "object",
229
+ "description": question.instructions,
230
+ "properties": {
231
+ "type": {"type": "string", "const": question.type},
232
+ "value": value,
233
+ "confidence": {"type": ["number", "null"], "minimum": 0, "maximum": 1},
234
+ "probabilities": probabilities,
235
+ **extra,
236
+ },
237
+ "required": ["type", "value", "confidence", "probabilities"],
238
+ }
239
+ return {
240
+ "type": "object",
241
+ "properties": properties,
242
+ "required": list(properties),
243
+ }
244
+
245
+
246
+ @dataclass(frozen=True)
247
+ class DecisionAnswer:
248
+ name: str
249
+ type: str
250
+ #: The answer itself: `True`/`False` for noul, the option for choice, the
251
+ #: weighted average (0-based) for score.
252
+ value: Any
253
+ confidence: float | None
254
+ probabilities: dict[str, float] = field(default_factory=dict)
255
+ legend: dict[str, str] = field(default_factory=dict)
256
+
257
+ def to_dict(self) -> dict[str, Any]:
258
+ payload: dict[str, Any] = {
259
+ "type": self.type,
260
+ "value": self.value,
261
+ "confidence": self.confidence,
262
+ "probabilities": dict(self.probabilities),
263
+ }
264
+ if self.legend:
265
+ payload["legend"] = dict(self.legend)
266
+ return payload
267
+
268
+
269
+ def _as_float(value: Any) -> float | None:
270
+ try:
271
+ return float(value)
272
+ except (TypeError, ValueError):
273
+ return None
274
+
275
+
276
+ def parse_answer(name: str, raw: Any) -> DecisionAnswer:
277
+ raw = raw if isinstance(raw, dict) else {}
278
+ answer_type = _clean_text(raw.get("type")).lower()
279
+ probabilities = {
280
+ str(key): float(value)
281
+ for key, value in (raw.get("probabilities") or {}).items()
282
+ if _as_float(value) is not None
283
+ }
284
+
285
+ if answer_type == QUESTION_NOUL:
286
+ # A noul answer carries only p(true); its confidence is not in the
287
+ # response, so it is derived the same way for every model: how far p
288
+ # is from a coin toss.
289
+ p_true = _as_float(raw.get("noul"))
290
+ if p_true is None:
291
+ return DecisionAnswer(name=name, type=answer_type, value=None, confidence=None)
292
+ return DecisionAnswer(
293
+ name=name,
294
+ type=answer_type,
295
+ value=p_true >= 0.5,
296
+ confidence=round(abs(2 * p_true - 1), 6),
297
+ probabilities={"true": p_true, "false": round(1 - p_true, 6)},
298
+ )
299
+
300
+ if answer_type == QUESTION_SCORE:
301
+ legend = {str(key): _clean_text(value) for key, value in (raw.get("legend") or {}).items()}
302
+ return DecisionAnswer(
303
+ name=name,
304
+ type=answer_type,
305
+ value=_as_float(raw.get("score")),
306
+ confidence=_as_float(raw.get("confidence")),
307
+ probabilities=probabilities,
308
+ legend=legend,
309
+ )
310
+
311
+ return DecisionAnswer(
312
+ name=name,
313
+ type=answer_type or QUESTION_CHOICE,
314
+ value=raw.get("choice"),
315
+ confidence=_as_float(raw.get("confidence")),
316
+ probabilities=probabilities,
317
+ )
318
+
319
+
320
+ @dataclass(frozen=True)
321
+ class DecisionResult:
322
+ answers: dict[str, DecisionAnswer]
323
+ #: The dated model that answered (`typesafe/jev-1.14-20261014`), not the key asked for.
324
+ model: str
325
+ provider: str | None
326
+ response_id: str | None
327
+ input_tokens: int
328
+ output_tokens: int
329
+ cost_usd: float | None
330
+ latency_ms: int
331
+
332
+ @classmethod
333
+ def from_response(cls, body: dict[str, Any], *, requested_model: str, latency_ms: int) -> "DecisionResult":
334
+ usage = body.get("usage") if isinstance(body.get("usage"), dict) else {}
335
+ raw_answers = body.get("answers") if isinstance(body.get("answers"), dict) else {}
336
+ return cls(
337
+ answers={name: parse_answer(name, raw) for name, raw in raw_answers.items()},
338
+ model=_clean_text(body.get("model")) or requested_model,
339
+ provider=_clean_text(body.get("provider")) or None,
340
+ response_id=_clean_text(body.get("id")) or None,
341
+ input_tokens=int(_as_float(usage.get("input_tokens")) or 0),
342
+ output_tokens=int(_as_float(usage.get("output_tokens")) or 0),
343
+ cost_usd=_as_float(usage.get("cost")),
344
+ latency_ms=int(latency_ms),
345
+ )
346
+
347
+ def answers_dict(self) -> dict[str, Any]:
348
+ return {name: answer.to_dict() for name, answer in self.answers.items()}
349
+
350
+ def to_dict(self) -> dict[str, Any]:
351
+ return {
352
+ "answers": self.answers_dict(),
353
+ "model": self.model,
354
+ "provider": self.provider,
355
+ "response_id": self.response_id,
356
+ "usage": {
357
+ "input_tokens": self.input_tokens,
358
+ "output_tokens": self.output_tokens,
359
+ "cost_usd": self.cost_usd,
360
+ },
361
+ "latency_ms": self.latency_ms,
362
+ }
363
+
364
+ def summary_text(self) -> str:
365
+ """One line per question, for the places that show text (history, logs)."""
366
+ lines = []
367
+ for name, answer in self.answers.items():
368
+ confidence = "" if answer.confidence is None else f" (confianza {answer.confidence:.2f})"
369
+ if answer.type == QUESTION_NOUL:
370
+ value = "sí" if answer.value else "no"
371
+ elif answer.type == QUESTION_SCORE and answer.value is not None:
372
+ value = f"{answer.value:.2f}"
373
+ else:
374
+ value = str(answer.value)
375
+ lines.append(f"{name}: {value}{confidence}")
376
+ return "\n".join(lines)
@@ -180,6 +180,25 @@ class LLMProxy:
180
180
  **request_kwargs,
181
181
  )
182
182
 
183
+ def resolve_client_config(
184
+ self,
185
+ company_short_name: str,
186
+ provider: str,
187
+ model_config: Dict[str, Any] | None = None,
188
+ ) -> dict:
189
+ """The credentials, endpoint, headers and timeouts a call to `provider` uses.
190
+
191
+ Public for the callers that reach a provider outside the chat adapters —
192
+ the decision models (BL-008) are served by OpenRouter on an endpoint the
193
+ OpenAI SDK does not speak — so they pay with the same key, through the
194
+ same gateway and with the same timeouts as every other call.
195
+ """
196
+ return self._get_client_config(company_short_name, provider, model_config=model_config)
197
+
198
+ @staticmethod
199
+ def wire_model(model: str, model_config: Dict[str, Any] | None) -> str:
200
+ return LLMProxy._wire_model(model, model_config)
201
+
183
202
  def describe_transport(self, company_short_name: str, model: str) -> str:
184
203
  provider = self._resolve_provider_for_company_model(
185
204
  company_short_name=company_short_name,
@@ -0,0 +1,177 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ """HTTP client for OpenRouter's decision models (BL-008).
7
+
8
+ Decision models are not served by the chat completions API, so the OpenAI SDK
9
+ the proxy uses for OpenRouter cannot call them: this is a plain HTTP call to
10
+ `/api/alpha/decisions`. Credentials, endpoint and timeouts are the ones the
11
+ company already has for `openrouter`, resolved by `LLMProxy`.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import time
18
+ from typing import Any
19
+ from urllib.parse import urlsplit
20
+
21
+ import httpx
22
+
23
+ from iatoolkit.common.decision_types import DecisionQuestion, DecisionResult
24
+ from iatoolkit.common.exceptions import IAToolkitException
25
+
26
+ DEFAULT_DECISIONS_PATH = "/api/alpha/decisions"
27
+ DEFAULT_BASE_URL = "https://openrouter.ai/api/v1"
28
+ RETRYABLE_STATUS = {408, 429, 500, 502, 503, 504}
29
+ DEFAULT_RETRY_ATTEMPTS = 2
30
+ RETRY_BACKOFF_SECONDS = 0.5
31
+
32
+
33
+ class OpenRouterDecisionClient:
34
+
35
+ def __init__(self, http_client: httpx.Client | None = None, sleep=time.sleep):
36
+ self._http_client = http_client
37
+ self._sleep = sleep
38
+
39
+ @staticmethod
40
+ def decisions_url(base_url: str | None, path: str | None = None) -> str:
41
+ """The decisions endpoint on the company's OpenRouter host.
42
+
43
+ The configured `base_url` is the chat one (`https://openrouter.ai/api/v1`);
44
+ the decisions router lives on the same host under its own path, so only
45
+ the origin is kept. A gateway in front of OpenRouter keeps working as long
46
+ as it forwards that path.
47
+ """
48
+ parts = urlsplit(str(base_url or DEFAULT_BASE_URL).strip() or DEFAULT_BASE_URL)
49
+ origin = f"{parts.scheme or 'https'}://{parts.netloc or 'openrouter.ai'}"
50
+ decision_path = str(path or DEFAULT_DECISIONS_PATH).strip() or DEFAULT_DECISIONS_PATH
51
+ if not decision_path.startswith("/"):
52
+ decision_path = f"/{decision_path}"
53
+ return f"{origin}{decision_path}"
54
+
55
+ def decide(
56
+ self,
57
+ *,
58
+ api_key: str,
59
+ url: str,
60
+ model: str,
61
+ state: Any,
62
+ questions: dict[str, DecisionQuestion],
63
+ provider_policy: dict[str, Any] | None = None,
64
+ default_headers: dict[str, str] | None = None,
65
+ connect_timeout_seconds: float = 10.0,
66
+ read_timeout_seconds: float = 60.0,
67
+ retry_attempts: int = DEFAULT_RETRY_ATTEMPTS,
68
+ user: str | None = None,
69
+ ) -> DecisionResult:
70
+ payload: dict[str, Any] = {
71
+ "model": model,
72
+ "state": state,
73
+ "questions": {name: question.to_wire() for name, question in questions.items()},
74
+ }
75
+ if provider_policy:
76
+ payload["provider"] = dict(provider_policy)
77
+ if user:
78
+ payload["user"] = user
79
+
80
+ headers = dict(default_headers or {})
81
+ headers["Authorization"] = f"Bearer {api_key}"
82
+ headers["Content-Type"] = "application/json"
83
+ timeout = httpx.Timeout(read_timeout_seconds, connect=connect_timeout_seconds)
84
+
85
+ attempts = max(0, int(retry_attempts)) + 1
86
+ started = time.monotonic()
87
+ last_error: str | None = None
88
+ for attempt in range(1, attempts + 1):
89
+ try:
90
+ response = self._post(url, payload, headers, timeout)
91
+ except httpx.HTTPError as exc:
92
+ last_error = f"{type(exc).__name__}: {exc}"
93
+ if attempt < attempts:
94
+ self._backoff(attempt)
95
+ continue
96
+ raise IAToolkitException(
97
+ IAToolkitException.ErrorType.LLM_ERROR,
98
+ f"Decision model '{model}' could not be reached: {last_error}",
99
+ ) from exc
100
+
101
+ if response.status_code == 200:
102
+ body = self._json_body(response)
103
+ if not isinstance(body, dict) or not isinstance(body.get("answers"), dict):
104
+ raise IAToolkitException(
105
+ IAToolkitException.ErrorType.LLM_ERROR,
106
+ f"Decision model '{model}' returned a response without answers.",
107
+ )
108
+ latency_ms = int((time.monotonic() - started) * 1000)
109
+ return DecisionResult.from_response(body, requested_model=model, latency_ms=latency_ms)
110
+
111
+ message = self._error_message(response)
112
+ if response.status_code in RETRYABLE_STATUS and attempt < attempts:
113
+ logging.warning(
114
+ "Decision model '%s' answered %s (attempt %s/%s): %s",
115
+ model, response.status_code, attempt, attempts, message,
116
+ )
117
+ last_error = message
118
+ self._backoff(attempt)
119
+ continue
120
+ raise self._error_for(model, response.status_code, message)
121
+
122
+ raise IAToolkitException( # pragma: no cover - the loop always returns or raises
123
+ IAToolkitException.ErrorType.LLM_ERROR,
124
+ f"Decision model '{model}' failed: {last_error}",
125
+ )
126
+
127
+ def _post(self, url: str, payload: dict, headers: dict, timeout: httpx.Timeout) -> httpx.Response:
128
+ if self._http_client is not None:
129
+ return self._http_client.post(url, json=payload, headers=headers, timeout=timeout)
130
+ with httpx.Client(timeout=timeout) as client:
131
+ return client.post(url, json=payload, headers=headers)
132
+
133
+ def _backoff(self, attempt: int) -> None:
134
+ self._sleep(RETRY_BACKOFF_SECONDS * (2 ** (attempt - 1)))
135
+
136
+ @staticmethod
137
+ def _json_body(response: httpx.Response) -> Any:
138
+ try:
139
+ return response.json()
140
+ except ValueError:
141
+ return None
142
+
143
+ @classmethod
144
+ def _error_message(cls, response: httpx.Response) -> str:
145
+ body = cls._json_body(response)
146
+ if isinstance(body, dict):
147
+ error = body.get("error")
148
+ if isinstance(error, dict) and error.get("message"):
149
+ return str(error["message"])
150
+ if isinstance(error, str):
151
+ return error
152
+ return (response.text or "").strip()[:300] or f"HTTP {response.status_code}"
153
+
154
+ @staticmethod
155
+ def _error_for(model: str, status_code: int, message: str) -> IAToolkitException:
156
+ if status_code == 404:
157
+ # What a data policy that leaves no endpoint looks like: OpenRouter
158
+ # found the model but no provider serving it under the policy sent.
159
+ return IAToolkitException(
160
+ IAToolkitException.ErrorType.MODEL,
161
+ f"Decision model '{model}' is not available under the company's data policy "
162
+ f"(llm.decisions.provider): {message}",
163
+ )
164
+ if status_code in (401, 403):
165
+ return IAToolkitException(
166
+ IAToolkitException.ErrorType.API_KEY,
167
+ f"OpenRouter refused the credentials for decision model '{model}': {message}",
168
+ )
169
+ if status_code == 400:
170
+ return IAToolkitException(
171
+ IAToolkitException.ErrorType.INVALID_PARAMETER,
172
+ f"Decision model '{model}' rejected the request: {message}",
173
+ )
174
+ return IAToolkitException(
175
+ IAToolkitException.ErrorType.LLM_ERROR,
176
+ f"Decision model '{model}' failed with HTTP {status_code}: {message}",
177
+ )
@@ -232,6 +232,7 @@ class LLMQueryRepo:
232
232
  dict(getattr(new_prompt, "runtime_policy", None) or {})
233
233
  or dict(getattr(prompt, "runtime_policy", None) or {})
234
234
  )
235
+ prompt.decision_spec = getattr(new_prompt, "decision_spec", None)
235
236
  else:
236
237
  if new_prompt.active is None:
237
238
  new_prompt.active = True
@@ -1070,11 +1070,23 @@ class Prompt(Base):
1070
1070
  llm_request_options = Column(JSON_NATIVE, nullable=False, default=dict)
1071
1071
  tool_policy = Column(JSON_NATIVE, nullable=False, default=dict)
1072
1072
  runtime_policy = Column(JSON_NATIVE, nullable=False, default=dict)
1073
+ #: The questions of a decision agent (BL-008), as
1074
+ #: {"questions": {name: {type, instructions, criteria}}}. Non-null marks the
1075
+ #: agent as a decision agent: its template renders the case a decision model
1076
+ #: answers these questions about. Set when the agent is created; NULL for
1077
+ #: every conversational agent.
1078
+ #: `none_as_null`: a conversational agent stores SQL NULL, not JSON `null`,
1079
+ #: so `decision_spec IS NULL` reads the type from SQL too.
1080
+ decision_spec = Column(JSON(none_as_null=True), nullable=True, default=None)
1073
1081
  created_at = Column(DateTime, default=datetime.now)
1074
1082
 
1075
1083
  def to_dict(self):
1076
1084
  return {column.key: getattr(self, column.key) for column in class_mapper(self.__class__).columns}
1077
1085
 
1086
+ @property
1087
+ def is_decision_agent(self) -> bool:
1088
+ return isinstance(self.decision_spec, dict)
1089
+
1078
1090
  company = relationship("Company", back_populates="prompts")
1079
1091
  category = relationship("PromptCategory", back_populates="prompts")
1080
1092
  resource_bindings = relationship(