diff --git a/python/.env.example b/python/.env.example index 14a6df4ef320..946e6f0746d2 100644 --- a/python/.env.example +++ b/python/.env.example @@ -1,4 +1,4 @@ OPENAI_API_KEY="" OPENAI_ORG_ID="" AZURE_OPENAI_API_KEY="" -AZURE_OPENAI_ENDPOINT="" \ No newline at end of file +AZURE_OPENAI_ENDPOINT="" diff --git a/python/semantic_kernel/ai/ai_exception.py b/python/semantic_kernel/ai/ai_exception.py index 0e1dcd9f1ce4..255e4a87ccfc 100644 --- a/python/semantic_kernel/ai/ai_exception.py +++ b/python/semantic_kernel/ai/ai_exception.py @@ -3,10 +3,8 @@ from enum import Enum from typing import Optional -from semantic_kernel.diagnostics.sk_exception import SKException - -class AIException(SKException): +class AIException(Exception): class ErrorCodes(Enum): # Unknown error. UnknownError = -1 diff --git a/python/semantic_kernel/ai/open_ai/services/azure_chat_completion.py b/python/semantic_kernel/ai/open_ai/services/azure_chat_completion.py index ed2ad1172550..bc3a755a4fb3 100644 --- a/python/semantic_kernel/ai/open_ai/services/azure_chat_completion.py +++ b/python/semantic_kernel/ai/open_ai/services/azure_chat_completion.py @@ -7,7 +7,6 @@ from semantic_kernel.ai.open_ai.services.open_ai_chat_completion import ( OpenAIChatCompletion, ) -from semantic_kernel.diagnostics.verify import Verify class AzureChatCompletion(OpenAIChatCompletion): @@ -22,12 +21,14 @@ def __init__( api_version: str = "2023-03-15-preview", logger: Optional[Logger] = None, ) -> None: - Verify.not_empty(deployment_name, "You must provide a deployment name") - Verify.not_empty(api_key, "The Azure API key cannot be empty") - Verify.not_empty(endpoint, "The Azure endpoint cannot be empty") - Verify.starts_with( - endpoint, "https://", "The Azure endpoint must start with https://" - ) + if not deployment_name: + raise ValueError("The deployment name cannot be `None` or empty") + if not api_key: + raise ValueError("The Azure API key cannot be `None` or empty`") + if not endpoint: + raise ValueError("The Azure endpoint cannot be `None` or empty") + if not endpoint.startswith("https://"): + raise ValueError("The Azure endpoint must start with https://") self._endpoint = endpoint self._api_version = api_version diff --git a/python/semantic_kernel/ai/open_ai/services/azure_text_completion.py b/python/semantic_kernel/ai/open_ai/services/azure_text_completion.py index 665629484bb4..b00bd111ded2 100644 --- a/python/semantic_kernel/ai/open_ai/services/azure_text_completion.py +++ b/python/semantic_kernel/ai/open_ai/services/azure_text_completion.py @@ -7,7 +7,6 @@ from semantic_kernel.ai.open_ai.services.open_ai_text_completion import ( OpenAITextCompletion, ) -from semantic_kernel.diagnostics.verify import Verify class AzureTextCompletion(OpenAITextCompletion): @@ -22,12 +21,14 @@ def __init__( api_version: str = "2022-12-01", logger: Optional[Logger] = None, ) -> None: - Verify.not_empty(deployment_name, "You must provide a deployment name") - Verify.not_empty(api_key, "The Azure API key cannot be empty") - Verify.not_empty(endpoint, "The Azure endpoint cannot be empty") - Verify.starts_with( - endpoint, "https://", "The Azure endpoint must start with https://" - ) + if not deployment_name: + raise ValueError("The deployment name cannot be `None` or empty") + if not api_key: + raise ValueError("The Azure API key cannot be `None` or empty`") + if not endpoint: + raise ValueError("The Azure endpoint cannot be `None` or empty") + if not endpoint.startswith("https://"): + raise ValueError("The Azure endpoint must start with https://") self._endpoint = endpoint self._api_version = api_version diff --git a/python/semantic_kernel/ai/open_ai/services/azure_text_embedding.py b/python/semantic_kernel/ai/open_ai/services/azure_text_embedding.py index 9b3b11cee7c3..2f5f0cded989 100644 --- a/python/semantic_kernel/ai/open_ai/services/azure_text_embedding.py +++ b/python/semantic_kernel/ai/open_ai/services/azure_text_embedding.py @@ -7,7 +7,6 @@ from semantic_kernel.ai.open_ai.services.open_ai_text_embedding import ( OpenAITextEmbedding, ) -from semantic_kernel.diagnostics.verify import Verify class AzureTextEmbedding(OpenAITextEmbedding): @@ -22,12 +21,14 @@ def __init__( api_version: str = "2022-12-01", logger: Optional[Logger] = None, ) -> None: - Verify.not_empty(deployment_name, "You must provide a deployment name") - Verify.not_empty(api_key, "The Azure API key cannot be empty") - Verify.not_empty(endpoint, "The Azure endpoint cannot be empty") - Verify.starts_with( - endpoint, "https://", "The Azure endpoint must start with https://" - ) + if not deployment_name: + raise ValueError("The deployment name cannot be `None` or empty") + if not api_key: + raise ValueError("The Azure API key cannot be `None` or empty`") + if not endpoint: + raise ValueError("The Azure endpoint cannot be `None` or empty") + if not endpoint.startswith("https://"): + raise ValueError("The Azure endpoint must start with https://") self._endpoint = endpoint self._api_version = api_version diff --git a/python/semantic_kernel/ai/open_ai/services/open_ai_chat_completion.py b/python/semantic_kernel/ai/open_ai/services/open_ai_chat_completion.py index 75ab0014cf07..7043d03d345b 100644 --- a/python/semantic_kernel/ai/open_ai/services/open_ai_chat_completion.py +++ b/python/semantic_kernel/ai/open_ai/services/open_ai_chat_completion.py @@ -6,7 +6,6 @@ from semantic_kernel.ai.ai_exception import AIException from semantic_kernel.ai.chat_completion_client_base import ChatCompletionClientBase from semantic_kernel.ai.chat_request_settings import ChatRequestSettings -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.utils.null_logger import NullLogger @@ -65,7 +64,8 @@ async def complete_chat_async( Returns: str -- The completed text. """ - Verify.not_null(request_settings, "The request settings cannot be empty") + if request_settings is None: + raise ValueError("The request settings cannot be `None`") if request_settings.max_tokens < 1: raise AIException( diff --git a/python/semantic_kernel/ai/open_ai/services/open_ai_text_completion.py b/python/semantic_kernel/ai/open_ai/services/open_ai_text_completion.py index 90668478768e..ee185b0ab794 100644 --- a/python/semantic_kernel/ai/open_ai/services/open_ai_text_completion.py +++ b/python/semantic_kernel/ai/open_ai/services/open_ai_text_completion.py @@ -6,7 +6,6 @@ from semantic_kernel.ai.ai_exception import AIException from semantic_kernel.ai.complete_request_settings import CompleteRequestSettings from semantic_kernel.ai.text_completion_client_base import TextCompletionClientBase -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.utils.null_logger import NullLogger @@ -65,8 +64,10 @@ async def complete_simple_async( Returns: str -- The completed text. """ - Verify.not_empty(prompt, "The prompt is empty") - Verify.not_null(request_settings, "The request settings cannot be empty") + if not prompt: + raise ValueError("The prompt cannot be `None` or empty") + if request_settings is None: + raise ValueError("The request settings cannot be `None`") if request_settings.max_tokens < 1: raise AIException( diff --git a/python/semantic_kernel/core_skills/text_memory_skill.py b/python/semantic_kernel/core_skills/text_memory_skill.py index ca2e893c62b2..1c478d4fe37b 100644 --- a/python/semantic_kernel/core_skills/text_memory_skill.py +++ b/python/semantic_kernel/core_skills/text_memory_skill.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.orchestration.sk_context import SKContext from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter @@ -44,19 +43,18 @@ async def recall_async(self, ask: str, context: SKContext) -> str: Returns: The nearest item from the memory store """ - Verify.not_null(context.variables, "Context has no variables") - assert context.variables is not None # for type checker - Verify.not_null(context.memory, "Context has no memory") - assert context.memory is not None # for type checker + if context.variables is None: + raise ValueError("Context has no variables") + if context.memory is None: + raise ValueError("Context has no memory") collection = ( context.variables[TextMemorySkill.COLLECTION_PARAM] if context.variables.contains_key(TextMemorySkill.COLLECTION_PARAM) else TextMemorySkill.DEFAULT_COLLECTION ) - Verify.not_empty( - collection, "Memory collection not defined for TextMemorySkill" - ) + if not collection: + raise ValueError("Memory collection not defined for TextMemorySkill") relevance = ( context.variables[TextMemorySkill.RELEVANCE_PARAM] @@ -104,26 +102,25 @@ async def save_async(self, text: str, context: SKContext): context -- Contains the 'collection' to save the information and unique 'key' to associate with the information """ - Verify.not_null(context.variables, "Context has no variables") - assert context.variables is not None # for type checker - Verify.not_null(context.memory, "Context has no memory") - assert context.memory is not None # for type checker + if context.variables is None: + raise ValueError("Context has no variables") + if context.memory is None: + raise ValueError("Context has no memory") collection = ( context.variables[TextMemorySkill.COLLECTION_PARAM] if context.variables.contains_key(TextMemorySkill.COLLECTION_PARAM) else TextMemorySkill.DEFAULT_COLLECTION ) - Verify.not_empty( - collection, "Memory collection not defined for TextMemorySkill" - ) + if not collection: + raise ValueError("Memory collection not defined for TextMemorySkill") key = ( context.variables[TextMemorySkill.KEY_PARAM] if context.variables.contains_key(TextMemorySkill.KEY_PARAM) else None ) - Verify.not_empty(key, "Memory key not defined for TextMemorySkill") - assert key is not None # for type checker + if not key: + raise ValueError("Memory key not defined for TextMemorySkill") await context.memory.save_information_async(collection, text=text, id=key) diff --git a/python/semantic_kernel/diagnostics/sk_exception.py b/python/semantic_kernel/diagnostics/sk_exception.py deleted file mode 100644 index b5743bb3229f..000000000000 --- a/python/semantic_kernel/diagnostics/sk_exception.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -from enum import Enum -from typing import Optional - - -class SKException(Exception): - """The base class for all semantic kernel exceptions.""" - - def __init__( - self, - error_code: Enum, - message: Optional[str] = None, - inner_exception: Optional[Exception] = None, - ) -> None: - """Initializes a new instance of the SKException class. - - Arguments: - error_code {Enum} -- The error code. - message {str} -- The error message. - inner_exception {Exception} -- The inner exception. - """ - super().__init__(self._build_message(error_code, message), inner_exception) - - def _build_message(self, error_code: Enum, message: Optional[str]) -> str: - if message is None: - return error_code.name - else: - return f"{error_code.name}: {message}" diff --git a/python/semantic_kernel/diagnostics/validation_exception.py b/python/semantic_kernel/diagnostics/validation_exception.py deleted file mode 100644 index 2a7a86889a5c..000000000000 --- a/python/semantic_kernel/diagnostics/validation_exception.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -from enum import Enum -from typing import Optional - -from semantic_kernel.diagnostics.sk_exception import SKException - - -class ValidationException(SKException): - class ErrorCodes(Enum): - # Unknown error. - UnknownError = -1 - # Null value. - NullValue = 0 - # Empty value. - EmptyValue = 1 - # Out of range. - OutOfRange = 2 - # Missing prefix. - MissingPrefix = 3 - # Directory not found. - DirectoryNotFound = 4 - - # The error code. - _error_code: ErrorCodes - - def __init__( - self, - error_code: ErrorCodes, - message: str, - inner_exception: Optional[Exception] = None, - ) -> None: - """Initializes a new instance of the ValidationException class. - - Arguments: - error_code {ErrorCodes} -- The error code. - message {str} -- The error message. - inner_exception {Exception} -- The inner exception. - """ - super().__init__(error_code, message, inner_exception) - self._error_code = error_code - - @property - def error_code(self) -> ErrorCodes: - """Gets the error code. - - Returns: - ErrorCodes -- The error code. - """ - return self._error_code diff --git a/python/semantic_kernel/diagnostics/verify.py b/python/semantic_kernel/diagnostics/verify.py deleted file mode 100644 index 219a535e1afb..000000000000 --- a/python/semantic_kernel/diagnostics/verify.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -import os -import re -from typing import Any, Optional - -from semantic_kernel.diagnostics.validation_exception import ValidationException -from semantic_kernel.kernel_exception import KernelException - - -class Verify: - @staticmethod - def not_null(value: Optional[Any], message: str) -> None: - if value is not None: - return - - raise ValidationException(ValidationException.ErrorCodes.NullValue, message) - - @staticmethod - def not_empty(value: Optional[str], message: str) -> None: - Verify.not_null(value, message) - if value.strip() != "": # type: ignore - return - - raise ValidationException(ValidationException.ErrorCodes.EmptyValue, message) - - @staticmethod - def valid_skill_name(value: Optional[str]) -> None: - Verify.not_empty(value, "The skill name cannot be empty") - - SKILL_NAME_REGEX = r"^[0-9A-Za-z_]*$" - if re.match(SKILL_NAME_REGEX, value): # type: ignore - return - - raise KernelException( - KernelException.ErrorCodes.InvalidFunctionDescription, - "A skill name can contain only latin letters, digits 0-9, " - f"and underscores: '{value}' is not a valid skill name.", - ) - - @staticmethod - def valid_function_name(value: Optional[str]) -> None: - Verify.not_empty(value, "The function name cannot be empty") - - FUNCTION_NAME_REGEX = r"^[0-9A-Za-z_]*$" - if re.match(FUNCTION_NAME_REGEX, value): # type: ignore - return - - raise KernelException( - KernelException.ErrorCodes.InvalidFunctionDescription, - "A function name can contain only latin letters, digits 0-9, " - f"and underscores: '{value}' is not a valid function name.", - ) - - @staticmethod - def valid_function_param_name(value: Optional[str]) -> None: - Verify.not_empty(value, "The function parameter name cannot be empty") - - FUNCTION_PARAM_NAME_REGEX = r"^[0-9A-Za-z_]*$" - if re.match(FUNCTION_PARAM_NAME_REGEX, value): # type: ignore - return - - raise KernelException( - KernelException.ErrorCodes.InvalidFunctionDescription, - "A function parameter name can contain only latin letters, " - f"digits 0-9, and underscores: '{value}' is not a valid " - f"function parameter name.", - ) - - @staticmethod - def starts_with(text: str, prefix: Optional[str], message: str) -> None: - Verify.not_empty(text, "The text to verify cannot be empty") - Verify.not_null(prefix, "The prefix to verify cannot be null") - - if text.startswith(prefix): # type: ignore - return - - raise ValidationException(ValidationException.ErrorCodes.MissingPrefix, message) - - @staticmethod - def directory_exists(path: str): - Verify.not_empty(path, "The path to verify cannot be empty") - - if os.path.isdir(path): - return - - raise ValidationException( - ValidationException.ErrorCodes.DirectoryNotFound, - f"Directory not found: '{path}'", - ) - - @staticmethod - def parameters_unique(parameters: list): # TODO: ParameterView - name_set = set() - - for parameter in parameters: - if parameter.name in name_set: - raise KernelException( - KernelException.ErrorCodes.InvalidFunctionDescription, - "The function has two or more parameters " - f"with the same name '{parameter.name}'", - ) - - Verify.not_empty(parameter.name, "The function parameter name is empty") - name_set.add(parameter.name.lower()) diff --git a/python/semantic_kernel/kernel.py b/python/semantic_kernel/kernel.py index 099560b5381e..5fda78de2feb 100644 --- a/python/semantic_kernel/kernel.py +++ b/python/semantic_kernel/kernel.py @@ -9,7 +9,6 @@ from semantic_kernel.ai.chat_request_settings import ChatRequestSettings from semantic_kernel.ai.complete_request_settings import CompleteRequestSettings from semantic_kernel.ai.text_completion_client_base import TextCompletionClientBase -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel_base import KernelBase from semantic_kernel.kernel_config import KernelConfig from semantic_kernel.kernel_exception import KernelException @@ -30,6 +29,7 @@ from semantic_kernel.template_engine.protocols.prompt_templating_engine import ( PromptTemplatingEngine, ) +from semantic_kernel.utils.validation import validate_function_name, validate_skill_name class Kernel(KernelBase, KernelExtensions): @@ -86,8 +86,8 @@ def register_semantic_function( skill_name = SkillCollection.GLOBAL_SKILL assert skill_name is not None # for type checker - Verify.valid_skill_name(skill_name) - Verify.valid_function_name(function_name) + validate_skill_name(skill_name) + validate_function_name(function_name) function = self._create_semantic_function( skill_name, function_name, function_config diff --git a/python/semantic_kernel/kernel_builder.py b/python/semantic_kernel/kernel_builder.py index f9bceefb1ef3..b9aa6b16b4ba 100644 --- a/python/semantic_kernel/kernel_builder.py +++ b/python/semantic_kernel/kernel_builder.py @@ -3,7 +3,6 @@ from logging import Logger from typing import Callable, Optional -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel import Kernel from semantic_kernel.kernel_config import KernelConfig from semantic_kernel.kernel_extensions import KernelExtensions @@ -30,22 +29,26 @@ def __init__( self._log = log def with_configuration(self, config: KernelConfig) -> "KernelBuilder": - Verify.not_null(config, "The configuration instance provided is None") + if config is None: + raise ValueError("The configuration instance cannot be `None`") self._config = config return self def with_memory(self, memory: SemanticTextMemoryBase) -> "KernelBuilder": - Verify.not_null(memory, "The memory instance provided is None") + if memory is None: + raise ValueError("The memory instance cannot be `None`") self._memory = memory return self def with_memory_storage(self, storage: MemoryStoreBase) -> "KernelBuilder": - Verify.not_null(storage, "The memory storage instance provided is None") + if storage is None: + raise ValueError("The memory storage instance cannot be `None`") self._memory_storage = storage return self def with_logger(self, log: Logger) -> "KernelBuilder": - Verify.not_null(log, "The logger instance provided is None") + if log is None: + raise ValueError("The logger instance cannot be `None`") self._log = log return self diff --git a/python/semantic_kernel/kernel_exception.py b/python/semantic_kernel/kernel_exception.py index 7c73a1a74552..cb39508f9a3f 100644 --- a/python/semantic_kernel/kernel_exception.py +++ b/python/semantic_kernel/kernel_exception.py @@ -3,10 +3,8 @@ from enum import Enum from typing import Optional -from semantic_kernel.diagnostics.sk_exception import SKException - -class KernelException(SKException): +class KernelException(Exception): class ErrorCodes(Enum): # Unknown error. UnknownError = -1 diff --git a/python/semantic_kernel/kernel_extensions/import_skills.py b/python/semantic_kernel/kernel_extensions/import_skills.py index 13fd5cd9ab83..06dda3077e70 100644 --- a/python/semantic_kernel/kernel_extensions/import_skills.py +++ b/python/semantic_kernel/kernel_extensions/import_skills.py @@ -4,7 +4,6 @@ import os from typing import Dict -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel_extensions.extends_kernel import ExtendsKernel from semantic_kernel.orchestration.sk_function_base import SKFunctionBase from semantic_kernel.semantic_functions.prompt_template import PromptTemplate @@ -14,6 +13,7 @@ from semantic_kernel.semantic_functions.semantic_function_config import ( SemanticFunctionConfig, ) +from semantic_kernel.utils.validation import validate_skill_name class ImportSkills(ExtendsKernel): @@ -25,10 +25,13 @@ def import_semantic_skill_from_directory( kernel = self.kernel() - Verify.valid_skill_name(skill_directory_name) + validate_skill_name(skill_directory_name) + skill_directory = os.path.join(parent_directory, skill_directory_name) skill_directory = os.path.abspath(skill_directory) - Verify.directory_exists(skill_directory) + + if not os.path.exists(skill_directory): + raise ValueError(f"Skill directory does not exist: {skill_directory_name}") skill = {} diff --git a/python/semantic_kernel/kernel_extensions/inline_definition.py b/python/semantic_kernel/kernel_extensions/inline_definition.py index 5270027f5838..2160d06752e6 100644 --- a/python/semantic_kernel/kernel_extensions/inline_definition.py +++ b/python/semantic_kernel/kernel_extensions/inline_definition.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, List, Optional from uuid import uuid4 -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel_extensions.extends_kernel import ExtendsKernel from semantic_kernel.semantic_functions.prompt_template import PromptTemplate from semantic_kernel.semantic_functions.prompt_template_config import ( @@ -12,6 +11,7 @@ from semantic_kernel.semantic_functions.semantic_function_config import ( SemanticFunctionConfig, ) +from semantic_kernel.utils.validation import validate_function_name, validate_skill_name if TYPE_CHECKING: from semantic_kernel.orchestration.sk_function_base import SKFunctionBase @@ -56,9 +56,9 @@ def create_semantic_function( ), ) - Verify.valid_function_name(function_name) + validate_function_name(function_name) if skill_name is not None: - Verify.valid_skill_name(skill_name) + validate_skill_name(skill_name) template = PromptTemplate( prompt_template, kernel.prompt_template_engine, config diff --git a/python/semantic_kernel/kernel_extensions/memory_configuration.py b/python/semantic_kernel/kernel_extensions/memory_configuration.py index 09a4def0661c..de7b018aef5d 100644 --- a/python/semantic_kernel/kernel_extensions/memory_configuration.py +++ b/python/semantic_kernel/kernel_extensions/memory_configuration.py @@ -5,7 +5,6 @@ from semantic_kernel.ai.embeddings.embedding_generator_base import ( EmbeddingGeneratorBase, ) -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel_extensions.extends_kernel import ExtendsKernel from semantic_kernel.memory.memory_store_base import MemoryStoreBase from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory @@ -21,19 +20,22 @@ def use_memory( if embeddings_generator is None: backend_label = kernel.config.get_embedding_backend_service_id() - Verify.not_empty(backend_label, "The embedding backend label is empty") + if not backend_label: + raise ValueError( + "The embedding backend label cannot be `None` or empty" + ) embeddings_backend = kernel.config.get_ai_backend( EmbeddingGeneratorBase, backend_label ) - Verify.not_null( - embeddings_backend, - f"AI configuration is missing for: {backend_label}", - ) + if not embeddings_backend: + raise ValueError(f"AI configuration is missing for: {backend_label}") embeddings_generator = embeddings_backend(kernel) - Verify.not_null(storage, "The storage instance provided is None") - Verify.not_null(embeddings_generator, "The embedding generator is None") + if storage is None: + raise ValueError("The storage instance provided cannot be `None`") + if embeddings_generator is None: + raise ValueError("The embedding generator cannot be `None`") kernel.register_memory(SemanticTextMemory(storage, embeddings_generator)) diff --git a/python/semantic_kernel/orchestration/context_variables.py b/python/semantic_kernel/orchestration/context_variables.py index 35696066971d..2a1479267ee5 100644 --- a/python/semantic_kernel/orchestration/context_variables.py +++ b/python/semantic_kernel/orchestration/context_variables.py @@ -3,8 +3,6 @@ from typing import Dict, Tuple -from semantic_kernel.diagnostics.verify import Verify - class ContextVariables: def __init__(self, content: str = "", variables: Dict[str, str] = None) -> None: @@ -36,7 +34,8 @@ def merge_or_overwrite( return self def set(self, name: str, value: str) -> "ContextVariables": - Verify.not_empty(name, "The variable name is empty") + if not name: + raise ValueError("The variable name cannot be `None` or empty") name = name.lower() if value is not None: @@ -58,7 +57,8 @@ def __getitem__(self, name: str) -> str: return self._variables[name] def __setitem__(self, name: str, value: str) -> None: - Verify.not_empty(name, "The variable name is empty") + if not name: + raise ValueError("The variable name cannot be `None` or empty") name = name.lower() self._variables[name] = value diff --git a/python/semantic_kernel/orchestration/sk_context.py b/python/semantic_kernel/orchestration/sk_context.py index 59de9c29f471..f2cc0eae2a6b 100644 --- a/python/semantic_kernel/orchestration/sk_context.py +++ b/python/semantic_kernel/orchestration/sk_context.py @@ -3,7 +3,6 @@ from logging import Logger from typing import Any, Literal, Optional, Tuple, Union -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel_exception import KernelException from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase from semantic_kernel.orchestration.context_variables import ContextVariables @@ -177,7 +176,8 @@ def func(self, skill_name: str, function_name: str): Returns: SKFunctionBase -- The function. """ - Verify.not_null(self._skill_collection, "The skill collection hasn't been set") + if self._skill_collection is None: + raise ValueError("The skill collection hasn't been set") assert self._skill_collection is not None # for type checker if self._skill_collection.has_native_function(skill_name, function_name): diff --git a/python/semantic_kernel/orchestration/sk_function.py b/python/semantic_kernel/orchestration/sk_function.py index a39f1ffc48c2..68c74cd7c6e3 100644 --- a/python/semantic_kernel/orchestration/sk_function.py +++ b/python/semantic_kernel/orchestration/sk_function.py @@ -8,7 +8,6 @@ from semantic_kernel.ai.chat_request_settings import ChatRequestSettings from semantic_kernel.ai.complete_request_settings import CompleteRequestSettings from semantic_kernel.ai.text_completion_client_base import TextCompletionClientBase -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel_exception import KernelException from semantic_kernel.memory.null_memory import NullMemory from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase @@ -43,7 +42,8 @@ class SKFunction(SKFunctionBase): @staticmethod def from_native_method(method, skill_name="", log=None) -> "SKFunction": - Verify.not_null(method, "Method is empty") + if method is None: + raise ValueError("Method cannot be `None`") assert method.__sk_function__ is not None, "Method is not a SK function" assert method.__sk_function_name__ is not None, "Method name is empty" @@ -88,10 +88,12 @@ def from_semantic_config( function_config: SemanticFunctionConfig, log: Optional[Logger] = None, ) -> "SKFunction": - Verify.not_null(function_config, "Function configuration is empty") + if function_config is None: + raise ValueError("Function configuration cannot be `None`") async def _local_func(client, request_settings, context): - Verify.not_null(client, "AI LLM backend is empty") + if client is None: + raise ValueError("AI LLM backend cannot be `None`") try: if function_config.has_chat_prompt: @@ -200,7 +202,8 @@ def set_default_skill_collection( def set_ai_backend( self, ai_backend: Callable[[], TextCompletionClientBase] ) -> "SKFunction": - Verify.not_null(ai_backend, "AI LLM backend factory is empty") + if ai_backend is None: + raise ValueError("AI LLM backend factory cannot be `None`") self._verify_is_semantic() self._ai_backend = ai_backend() return self @@ -208,19 +211,22 @@ def set_ai_backend( def set_chat_backend( self, chat_backend: Callable[[], ChatCompletionClientBase] ) -> "SKFunction": - Verify.not_null(chat_backend, "Chat LLM backend factory is empty") + if chat_backend is None: + raise ValueError("Chat LLM backend factory cannot be `None`") self._verify_is_semantic() self._chat_backend = chat_backend() return self def set_ai_configuration(self, settings: CompleteRequestSettings) -> "SKFunction": - Verify.not_null(settings, "AI LLM request settings are empty") + if settings is None: + raise ValueError("AI LLM request settings cannot be `None`") self._verify_is_semantic() self._ai_request_settings = settings return self def set_chat_configuration(self, settings: ChatRequestSettings) -> "SKFunction": - Verify.not_null(settings, "Chat LLM request settings are empty") + if settings is None: + raise ValueError("Chat LLM request settings cannot be `None`") self._verify_is_semantic() self._chat_request_settings = settings return self @@ -242,7 +248,8 @@ async def invoke_async( log: Optional[Logger] = None, ) -> SKContext: if context is None: - Verify.not_null(self._skill_collection, "Skill collection is empty") + if self._skill_collection is None: + raise ValueError("Skill collection cannot be `None`") assert self._skill_collection is not None context = SKContext( diff --git a/python/semantic_kernel/orchestration/sk_function_base.py b/python/semantic_kernel/orchestration/sk_function_base.py index a49d20dc17c5..7bb06ea32292 100644 --- a/python/semantic_kernel/orchestration/sk_function_base.py +++ b/python/semantic_kernel/orchestration/sk_function_base.py @@ -18,6 +18,10 @@ class SKFunctionBase(ABC): + FUNCTION_PARAM_NAME_REGEX = r"^[0-9A-Za-z_]*$" + FUNCTION_NAME_REGEX = r"^[0-9A-Za-z_]*$" + SKILL_NAME_REGEX = r"^[0-9A-Za-z_]*$" + @property @abstractmethod def name(self) -> str: diff --git a/python/semantic_kernel/skill_definition/function_view.py b/python/semantic_kernel/skill_definition/function_view.py index 0d64cec15c93..12539178487e 100644 --- a/python/semantic_kernel/skill_definition/function_view.py +++ b/python/semantic_kernel/skill_definition/function_view.py @@ -2,8 +2,8 @@ from typing import List -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.skill_definition.parameter_view import ParameterView +from semantic_kernel.utils.validation import validate_function_name class FunctionView: @@ -23,7 +23,7 @@ def __init__( is_semantic: bool, is_asynchronous: bool = True, ) -> None: - Verify.valid_function_name(name) + validate_function_name(name) self._name = name self._skill_name = skill_name @@ -58,7 +58,8 @@ def is_asynchronous(self) -> bool: @name.setter def name(self, value: str) -> None: - Verify.valid_function_name(value) + validate_function_name(value) + self._name = value @skill_name.setter diff --git a/python/semantic_kernel/skill_definition/parameter_view.py b/python/semantic_kernel/skill_definition/parameter_view.py index c39c145148f2..41ad4a19401a 100644 --- a/python/semantic_kernel/skill_definition/parameter_view.py +++ b/python/semantic_kernel/skill_definition/parameter_view.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from semantic_kernel.diagnostics.verify import Verify +from semantic_kernel.utils.validation import validate_function_param_name class ParameterView: @@ -9,7 +9,7 @@ class ParameterView: _default_value: str def __init__(self, name: str, description: str, default_value: str) -> None: - Verify.valid_function_param_name(name) + validate_function_param_name(name) self._name = name self._description = description @@ -29,7 +29,7 @@ def default_value(self) -> str: @name.setter def name(self, value: str) -> None: - Verify.valid_function_param_name(value) + validate_function_param_name(value) self._name = value @description.setter diff --git a/python/semantic_kernel/skill_definition/skill_collection.py b/python/semantic_kernel/skill_definition/skill_collection.py index 448d7c0e0f10..b4977ead4641 100644 --- a/python/semantic_kernel/skill_definition/skill_collection.py +++ b/python/semantic_kernel/skill_definition/skill_collection.py @@ -3,7 +3,6 @@ from logging import Logger from typing import TYPE_CHECKING, Dict, Literal, Optional, Tuple -from semantic_kernel.diagnostics.verify import Verify from semantic_kernel.kernel_exception import KernelException from semantic_kernel.skill_definition.functions_view import FunctionsView from semantic_kernel.skill_definition.read_only_skill_collection import ( @@ -35,7 +34,8 @@ def __init__(self, log: Optional[Logger] = None) -> None: self._skill_collection = {} def add_semantic_function(self, function: "SKFunctionBase") -> None: - Verify.not_null(function, "The function provided is None") + if function is None: + raise ValueError("The function provided cannot be `None`") s_name, f_name = function.skill_name, function.name s_name, f_name = s_name.lower(), f_name.lower() @@ -46,7 +46,8 @@ def add_semantic_function(self, function: "SKFunctionBase") -> None: self._skill_collection[s_name][f_name] = function def add_native_function(self, function: "SKFunctionBase") -> None: - Verify.not_null(function, "The function provided is None") + if function is None: + raise ValueError("The function provided cannot be `None`") s_name, f_name = function.skill_name, function.name s_name, f_name = self._normalize_names(s_name, f_name, True) @@ -141,8 +142,8 @@ def _normalize_names( if s_name is None and allow_substitution: s_name = self.GLOBAL_SKILL - Verify.not_null(s_name, "The skill name provided is None") - assert s_name is not None # to make type checker happy + if s_name is None: + raise ValueError("The skill name provided cannot be `None`") s_name, f_name = s_name.lower(), f_name.lower() return s_name, f_name diff --git a/python/semantic_kernel/template_engine/blocks/function_id_block.py b/python/semantic_kernel/template_engine/blocks/function_id_block.py index 655eea90ba1f..241c558d0b83 100644 --- a/python/semantic_kernel/template_engine/blocks/function_id_block.py +++ b/python/semantic_kernel/template_engine/blocks/function_id_block.py @@ -39,6 +39,8 @@ def is_valid(self) -> Tuple[bool, str]: return False, error_msg if not re_match(r"^[a-zA-Z0-9_.]*$", self.content): + # NOTE: this is not quite the same as + # utils.validation.validate_function_name error_msg = ( f"The function identifier '{self.content}' contains invalid " "characters. Only alphanumeric chars, underscore and a single " diff --git a/python/semantic_kernel/utils/validation.py b/python/semantic_kernel/utils/validation.py new file mode 100644 index 000000000000..a2807d61a68e --- /dev/null +++ b/python/semantic_kernel/utils/validation.py @@ -0,0 +1,72 @@ +# Copyright (c) Microsoft. All rights reserved. + +from re import match as re_match +from typing import Optional + + +def validate_skill_name(value: Optional[str]) -> None: + """ + Validates that the skill name is valid. + + Valid skill names are non-empty and + match the regex: [0-9A-Za-z_]* + + :param value: The skill name to validate. + + :raises ValueError: If the skill name is invalid. + """ + if not value: + raise ValueError("The skill name cannot be `None` or empty") + + SKILL_NAME_REGEX = r"^[0-9A-Za-z_]*$" + if not re_match(SKILL_NAME_REGEX, value): + raise ValueError( + f"Invalid skill name: {value}. Skill " + f"names may only contain ASCII letters, " + f"digits, and underscores." + ) + + +def validate_function_name(value: Optional[str]) -> None: + """ + Validates that the function name is valid. + + Valid function names are non-empty and + match the regex: [0-9A-Za-z_]* + + :param value: The function name to validate. + + :raises ValueError: If the function name is invalid. + """ + if not value: + raise ValueError("The function name cannot be `None` or empty") + + FUNCTION_NAME_REGEX = r"^[0-9A-Za-z_]*$" + if not re_match(FUNCTION_NAME_REGEX, value): + raise ValueError( + f"Invalid function name: {value}. Function " + f"names may only contain ASCII letters, " + f"digits, and underscores." + ) + + +def validate_function_param_name(value: Optional[str]) -> None: + """ + Validates that the function parameter name is valid. + + Valid function parameter names are non-empty and + match the regex: [0-9A-Za-z_]* + + :param value: The function parameter name to validate. + + :raises ValueError: If the function parameter name is invalid. + """ + if not value: + raise ValueError("The function parameter name cannot be `None` or empty") + + FUNCTION_PARAM_NAME_REGEX = r"^[0-9A-Za-z_]*$" + if not re_match(FUNCTION_PARAM_NAME_REGEX, value): + raise ValueError( + f"Invalid function parameter name: {value}. Function parameter " + f"names may only contain ASCII letters, digits, and underscores." + )