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.
- {iatoolkit-2.76.10/src/iatoolkit.egg-info → iatoolkit-2.77.1}/PKG-INFO +1 -1
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/__init__.py +1 -1
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/access.py +10 -3
- iatoolkit-2.77.1/src/iatoolkit/common/decision_types.py +376 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_proxy.py +19 -0
- iatoolkit-2.77.1/src/iatoolkit/infra/openrouter_decision_client.py +177 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/llm_query_repo.py +1 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/models.py +12 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/configuration_service.py +14 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/context_builder_service.py +42 -14
- iatoolkit-2.77.1/src/iatoolkit/services/decision_service.py +190 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/llm_client_service.py +141 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/prompt_service.py +114 -1
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/query_service.py +85 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_agent_runner.py +4 -2
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/account_view.py +3 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/prompt_api_view.py +8 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1/src/iatoolkit.egg-info}/PKG-INFO +1 -1
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit.egg-info/SOURCES.txt +3 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/LICENSE +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/pyproject.toml +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/readme.md +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/setup.cfg +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/base_company.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/cli_commands.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/bridge_validation.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/company_paths.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/exceptions.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/history_compaction_trigger.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/memory_tools.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/model_registry.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/routes.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/search_terms.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/secret_resolver.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/session_manager.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/text_splitter.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/url_safety.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/common/util.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/company_registry.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/history_compaction.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_compiler.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_correction.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_document_ingest.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_image_ingest.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_meeting_ingest.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents/memory_question_review.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_agents_pack.yaml +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/config/system_tools_pack.yaml +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/core.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/call_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/google_auth_client.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/google_chat_app.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/inference_handler.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/llm_response.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/infra/redis_session_manager.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/locales/en.yaml +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/locales/es.yaml +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/advisory_lock.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/api_key_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/bridge_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/database_manager.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/document_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/memory_question_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/memory_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/memory_usage_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/profile_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/repositories/vs_repo.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/runtime_logging.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/api_key_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/attachment_policy_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/auth_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/branding_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/company_context_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/dispatcher_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/embedding_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/excel_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/file_processor_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/history_compaction_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/history_manager_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/http_tool_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/i18n_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/inference_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/jwt_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/knowledge_base_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/language_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/license_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/mail_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/mcp_token_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_compiler_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_question_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_sources.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_usage_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/memory_wiki_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/noop_history_compaction_trigger.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/operator_identity_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/contracts.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/parsers/validator.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/pdf_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/profile_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/prompt_resource_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/resource_access_scope.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/signup_guard_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_catalog_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_dataset_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/sql_source_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/storage_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/structured_output_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_agent_catalog.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/system_tools.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/telemetry_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/tenant_wiki_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/tool_output_contract.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/tool_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/user_feedback_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/user_session_context_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/visual_kb_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/visual_tool_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/warmup_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/web_search_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/services/wiki_page_index_service.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/favicon.ico +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/favicon.png +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/fernando.jpeg +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_filepond.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_header_navigation.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_help_content.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_history_button.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_main.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/account.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/chat_modal.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/chat_public.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/documents.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/landing_page.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/llm_output.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/onboarding.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/static/styles/wiki_page.css +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/_chat_header.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/_company_header.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/_login_widget.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/account.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/base.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/change_password.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/chat.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/chat_modals.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/error.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/forgot_password.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/home_hosted_default.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/login_global.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/base.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/letter.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/report.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/pdf/simple.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/signup.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/templates/wiki_page.html +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/__init__.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/api_key_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/base_login_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/categories_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/change_password_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/chat_context_preview_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/chat_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/configuration_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/connectors_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/embedding_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/forgot_password_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/help_content_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/history_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/home_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/init_context_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/llmquery_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/load_document_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/login_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/logout_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/memory_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/profile_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/prompt_context_preview_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/prompt_resource_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/rag_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/root_redirect_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/signup_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/static_page_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/tool_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/users_api_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/verify_user_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit/views/wiki_page_view.py +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit.egg-info/requires.txt +0 -0
- {iatoolkit-2.76.10 → iatoolkit-2.77.1}/src/iatoolkit.egg-info/top_level.txt +0 -0
|
@@ -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
|
|
188
|
-
|
|
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(
|