diff --git a/python/notebooks/01-basic-loading-the-kernel.ipynb b/python/notebooks/01-basic-loading-the-kernel.ipynb index dc9ca306d5..da00f754ea 100644 --- a/python/notebooks/01-basic-loading-the-kernel.ipynb +++ b/python/notebooks/01-basic-loading-the-kernel.ipynb @@ -56,17 +56,6 @@ "kernel_1 = sk.Kernel()" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Instance with a custom logger\n", - "my_logger = sk.NullLogger()\n", - "kernel_2 = sk.Kernel(log=my_logger)" - ] - }, { "attachments": {}, "cell_type": "markdown", diff --git a/python/samples/kernel-syntax-examples/setup_logging.py b/python/samples/kernel-syntax-examples/setup_logging.py new file mode 100644 index 0000000000..8823dd832b --- /dev/null +++ b/python/samples/kernel-syntax-examples/setup_logging.py @@ -0,0 +1,35 @@ +# Copyright (c) Microsoft. All rights reserved. +import logging + +import semantic_kernel as sk +from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion +from semantic_kernel.utils.logging import setup_logging + + +async def main(): + setup_logging() + + # Set the logging level for semantic_kernel.kernel to DEBUG. + logging.getLogger("kernel").setLevel(logging.DEBUG) + + kernel = sk.Kernel() + + api_key, org_id = sk.openai_settings_from_dot_env() + + kernel.add_chat_service( + "chat-gpt", OpenAIChatCompletion("gpt-3.5-turbo", api_key, org_id) + ) + + skill = kernel.import_semantic_skill_from_directory( + "../../samples/skills", "FunSkill" + ) + + joke_function = skill["Joke"] + + print(joke_function("time travel to dinosaur age")) + + +if __name__ == "__main__": + import asyncio + + asyncio.run(main()) diff --git a/python/semantic_kernel/__init__.py b/python/semantic_kernel/__init__.py index 5e68231c0c..6d799c2ff7 100644 --- a/python/semantic_kernel/__init__.py +++ b/python/semantic_kernel/__init__.py @@ -13,6 +13,7 @@ from semantic_kernel.semantic_functions.prompt_template_config import ( from semantic_kernel.semantic_functions.semantic_function_config import ( SemanticFunctionConfig, ) +from semantic_kernel.utils.logging import setup_logging from semantic_kernel.utils.null_logger import NullLogger from semantic_kernel.utils.settings import ( azure_aisearch_settings_from_dot_env, @@ -51,4 +52,5 @@ __all__ = [ "SKContext", "memory", "core_skills", + "setup_logging", ] diff --git a/python/semantic_kernel/connectors/ai/ai_service_client_base.py b/python/semantic_kernel/connectors/ai/ai_service_client_base.py index c593b755e9..5ff7947bdf 100644 --- a/python/semantic_kernel/connectors/ai/ai_service_client_base.py +++ b/python/semantic_kernel/connectors/ai/ai_service_client_base.py @@ -1,30 +1,19 @@ # Copyright (c) Microsoft. All rights reserved. from abc import ABC -from logging import Logger -from typing import Optional -from pydantic import Field, constr, field_validator +from pydantic import constr from semantic_kernel.sk_pydantic import SKBaseModel -from semantic_kernel.utils.null_logger import NullLogger class AIServiceClientBase(SKBaseModel, ABC): """Base class for all AI Services. - Has a ai_model_id and a logger, any other fields have to be defined by the subclasses. + Has a ai_model_id, any other fields have to be defined by the subclasses. The ai_model_id can refer to a specific model, like 'gpt-35-turbo' for OpenAI, or can just be a string that is used to identify the service. """ ai_model_id: constr(strip_whitespace=True, min_length=1) - log: Optional[Logger] = Field(default_factory=NullLogger) - - @field_validator("log") - @classmethod - def validate_log(cls, v): - if v is None: - return NullLogger() - return v diff --git a/python/semantic_kernel/connectors/ai/chat_completion_client_base.py b/python/semantic_kernel/connectors/ai/chat_completion_client_base.py index dc7de0a53f..d036c389de 100644 --- a/python/semantic_kernel/connectors/ai/chat_completion_client_base.py +++ b/python/semantic_kernel/connectors/ai/chat_completion_client_base.py @@ -1,8 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. from abc import ABC, abstractmethod -from logging import Logger -from typing import TYPE_CHECKING, AsyncGenerator, List, Optional, Union +from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Union if TYPE_CHECKING: from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings @@ -15,7 +14,7 @@ class ChatCompletionClientBase(ABC): self, messages: List["ChatMessage"], settings: "ChatRequestSettings", - logger: Optional[Logger] = None, + logger: Optional[Any] = None, ) -> Union[str, List[str]]: """ This is the method that is called from the kernel to get a response from a chat-optimized LLM. @@ -24,7 +23,7 @@ class ChatCompletionClientBase(ABC): messages {List[ChatMessage]} -- A list of chat messages, that can be rendered into a set of messages, from system, user, assistant and function. settings {ChatRequestSettings} -- Settings for the request. - logger {Logger} -- A logger to use for logging. + logger {Logger} -- A logger to use for logging. (Deprecated) Returns: Union[str, List[str]] -- A string or list of strings representing the response(s) from the LLM. @@ -36,7 +35,7 @@ class ChatCompletionClientBase(ABC): self, messages: List["ChatMessage"], settings: "ChatRequestSettings", - logger: Optional[Logger] = None, + logger: Optional[Any] = None, ) -> AsyncGenerator[Union[str, List[str]], None]: """ This is the method that is called from the kernel to get a stream response from a chat-optimized LLM. @@ -45,7 +44,7 @@ class ChatCompletionClientBase(ABC): messages {List[ChatMessage]} -- A list of chat messages, that can be rendered into a set of messages, from system, user, assistant and function. settings {ChatRequestSettings} -- Settings for the request. - logger {Logger} -- A logger to use for logging. + logger {Logger} -- A logger to use for logging. (Deprecated) Yields: A stream representing the response(s) from the LLM. diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py index 187bcf4e7e..82d7421c27 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import List, Optional, Tuple, Union +import logging +from typing import Any, List, Optional, Tuple, Union import google.generativeai as palm from google.generativeai.types import ChatResponse, ExampleOptions, MessagePromptOptions @@ -20,6 +20,8 @@ from semantic_kernel.connectors.ai.text_completion_client_base import ( TextCompletionClientBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class GooglePalmChatCompletion( ChatCompletionClientBase, TextCompletionClientBase, AIServiceClientBase @@ -32,7 +34,7 @@ class GooglePalmChatCompletion( ai_model_id: str, api_key: str, message_history: Optional[ChatResponse] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ): """ Initializes a new instance of the GooglePalmChatCompletion class. @@ -43,13 +45,16 @@ class GooglePalmChatCompletion( api_key {str} -- GooglePalm API key, see https://developers.generativeai.google/products/palm message_history {Optional[ChatResponse]} -- The message history to use for context. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) + log {Optional[Any]} -- A logger to use for logging. (Optional) """ super().__init__( ai_model_id=ai_model_id, api_key=api_key, - log=log, ) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) self._message_history = message_history async def complete_chat_async( @@ -89,8 +94,12 @@ class GooglePalmChatCompletion( self, prompt: str, request_settings: CompleteRequestSettings, - logger: Optional[Logger] = None, + **kwargs, ) -> Union[str, List[str]]: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) prompt_to_message = [("user", prompt)] chat_settings = ChatRequestSettings( temperature=request_settings.temperature, @@ -118,8 +127,12 @@ class GooglePalmChatCompletion( self, prompt: str, request_settings: CompleteRequestSettings, - logger: Optional[Logger] = None, + **kwargs, ): + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) raise NotImplementedError( "Google Palm API does not currently support streaming" ) diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py index 85653d9ef6..f5e08438fe 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import List, Optional, Union +import logging +from typing import Any, List, Optional, Union import google.generativeai as palm from pydantic import constr @@ -15,11 +15,13 @@ from semantic_kernel.connectors.ai.text_completion_client_base import ( TextCompletionClientBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class GooglePalmTextCompletion(TextCompletionClientBase, AIServiceClientBase): api_key: constr(strip_whitespace=True, min_length=1) - def __init__(self, ai_model_id: str, api_key: str, log: Optional[Logger] = None): + def __init__(self, ai_model_id: str, api_key: str, log: Optional[Any] = None): """ Initializes a new instance of the GooglePalmTextCompletion class. @@ -28,16 +30,24 @@ class GooglePalmTextCompletion(TextCompletionClientBase, AIServiceClientBase): https://developers.generativeai.google/models/language api_key {str} -- GooglePalm API key, see https://developers.generativeai.google/products/palm - log {Optional[Logger]} -- The logger instance to use. (Optional) + log {Optional[Any]} -- The logger instance to use. (Optional) (Deprecated) """ - super().__init__(ai_model_id=ai_model_id, api_key=api_key, log=log) + super().__init__(ai_model_id=ai_model_id, api_key=api_key) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) async def complete_async( self, prompt: str, request_settings: CompleteRequestSettings, - logger: Optional[Logger] = None, + **kwargs, ) -> Union[str, List[str]]: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) response = await self._send_completion_request(prompt, request_settings) if request_settings.number_of_responses > 1: @@ -48,8 +58,12 @@ class GooglePalmTextCompletion(TextCompletionClientBase, AIServiceClientBase): self, prompt: str, request_settings: CompleteRequestSettings, - logger: Optional[Logger] = None, + **kwargs, ): + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) raise NotImplementedError( "Google Palm API does not currently support streaming" ) diff --git a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py index 59b078a823..89b5056391 100644 --- a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py +++ b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from threading import Thread from typing import Any, Dict, List, Literal, Optional, Union @@ -16,6 +16,8 @@ from semantic_kernel.connectors.ai.text_completion_client_base import ( TextCompletionClientBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase): task: Literal["summarization", "text-generation", "text2text-generation"] @@ -27,7 +29,7 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase): ai_model_id: str, task: Optional[str] = "text2text-generation", device: Optional[int] = -1, - log: Optional[Logger] = None, + log: Optional[Any] = None, model_kwargs: Optional[Dict[str, Any]] = None, pipeline_kwargs: Optional[Dict[str, Any]] = None, ) -> None: @@ -46,7 +48,7 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase): - text-generation: takes incomplete text and returns a set of completion candidates. - text2text-generation (default): takes an input prompt and returns a completion. text2text-generation is the default as it behaves more like GPT-3+. - log {Optional[Logger]} -- Logger instance. + log -- Logger instance. (Deprecated) model_kwargs {Optional[Dict[str, Any]]} -- Additional dictionary of keyword arguments passed along to the model's `from_pretrained(..., **model_kwargs)` function. pipeline_kwargs {Optional[Dict[str, Any]]} -- Additional keyword arguments passed along @@ -68,15 +70,22 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase): model_kwargs=model_kwargs, **pipeline_kwargs or {}, ), - log=log, ) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) async def complete_async( self, prompt: str, request_settings: CompleteRequestSettings, - logger: Optional[Logger] = None, + **kwargs, ) -> Union[str, List[str]]: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) try: generation_config = transformers.GenerationConfig( temperature=request_settings.temperature, @@ -113,7 +122,7 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase): self, prompt: str, request_settings: CompleteRequestSettings, - logger: Optional[Logger] = None, + **kwargs, ): """ Streams a text completion using a Hugging Face model. @@ -126,6 +135,10 @@ class HuggingFaceTextCompletion(TextCompletionClientBase, AIServiceClientBase): Yields: str -- Completion result. """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if request_settings.number_of_responses > 1: raise AIException( AIException.ErrorCodes.InvalidConfiguration, diff --git a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py index 5768316205..eb6d06d135 100644 --- a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import List, Optional +import logging +from typing import Any, List, Optional import sentence_transformers import torch @@ -13,6 +13,8 @@ from semantic_kernel.connectors.ai.embeddings.embedding_generator_base import ( EmbeddingGeneratorBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class HuggingFaceTextEmbedding(EmbeddingGeneratorBase, AIServiceClientBase): device: str @@ -22,7 +24,7 @@ class HuggingFaceTextEmbedding(EmbeddingGeneratorBase, AIServiceClientBase): self, ai_model_id: str, device: Optional[int] = -1, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initializes a new instance of the HuggingFaceTextEmbedding class. @@ -31,16 +33,19 @@ class HuggingFaceTextEmbedding(EmbeddingGeneratorBase, AIServiceClientBase): ai_model_id {str} -- Hugging Face model card string, see https://huggingface.co/sentence-transformers device {Optional[int]} -- Device to run the model on, -1 for CPU, 0+ for GPU. - log {Optional[Logger]} -- Logger instance. + log -- The logger instance to use. (Optional) (Deprecated) Note that this model will be downloaded from the Hugging Face model hub. """ + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) resolved_device = ( f"cuda:{device}" if device >= 0 and torch.cuda.is_available() else "cpu" ) super().__init__( ai_model_id=ai_model_id, - log=log, device=resolved_device, generator=sentence_transformers.SentenceTransformer( model_name_or_path=ai_model_id, device=resolved_device @@ -58,7 +63,7 @@ class HuggingFaceTextEmbedding(EmbeddingGeneratorBase, AIServiceClientBase): ndarray -- Embeddings for the texts. """ try: - self.log.info(f"Generating embeddings for {len(texts)} texts") + logger.info(f"Generating embeddings for {len(texts)} texts") embeddings = self.generator.encode(texts) return array(embeddings) except Exception as e: diff --git a/python/semantic_kernel/connectors/ai/open_ai/semantic_functions/open_ai_chat_prompt_template.py b/python/semantic_kernel/connectors/ai/open_ai/semantic_functions/open_ai_chat_prompt_template.py index 6b4b4d234f..b8b2e6f0d9 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/semantic_functions/open_ai_chat_prompt_template.py +++ b/python/semantic_kernel/connectors/ai/open_ai/semantic_functions/open_ai_chat_prompt_template.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import Any, Dict, List, Optional from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import FunctionCall @@ -16,6 +16,8 @@ from semantic_kernel.template_engine.protocols.prompt_templating_engine import ( PromptTemplatingEngine, ) +logger: logging.Logger = logging.getLogger(__name__) + class OpenAIChatPromptTemplate(ChatPromptTemplate): def add_function_response_message(self, name: str, content: Any) -> None: @@ -38,16 +40,14 @@ class OpenAIChatPromptTemplate(ChatPromptTemplate): """ name = kwargs.get("name") if name is not None and role != "function": - self._log.warning("name is only used with role: function, ignoring") + logger.warning("name is only used with role: function, ignoring") name = None function_call = kwargs.get("function_call") if function_call is not None and role != "assistant": - self._log.warning( - "function_call is only used with role: assistant, ignoring" - ) + logger.warning("function_call is only used with role: assistant, ignoring") function_call = None if function_call and not isinstance(function_call, FunctionCall): - self._log.warning( + logger.warning( "function_call is not a FunctionCall, ignoring: %s", function_call ) function_call = None @@ -69,7 +69,7 @@ class OpenAIChatPromptTemplate(ChatPromptTemplate): template: str, template_engine: PromptTemplatingEngine, prompt_config: PromptTemplateConfig, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> "OpenAIChatPromptTemplate": """Restore a ChatPromptTemplate from a list of role and message pairs. @@ -77,7 +77,11 @@ class OpenAIChatPromptTemplate(ChatPromptTemplate): that takes precedence over the first message in the list of messages, if that is a system message. """ - chat_template = cls(template, template_engine, prompt_config, log) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + chat_template = cls(template, template_engine, prompt_config) if ( prompt_config.completion.chat_system_prompt and messages[0]["role"] == "system" @@ -87,7 +91,7 @@ class OpenAIChatPromptTemplate(ChatPromptTemplate): existing_system_message["message"] != prompt_config.completion.chat_system_prompt ): - chat_template._log.info( + logger.info( "Overriding system prompt with chat_system_prompt, old system message: %s, new system message: %s", existing_system_message["message"], prompt_config.completion.chat_system_prompt, diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py index dc759b3e2f..4e45c0fc00 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft. All rights reserved. +import logging from dataclasses import asdict -from logging import Logger from typing import ( Any, AsyncGenerator, @@ -39,6 +39,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base from semantic_kernel.connectors.ai.open_ai.utils import _parse_message from semantic_kernel.sk_pydantic import HttpsUrl +logger: logging.Logger = logging.getLogger(__name__) + class AzureChatCompletion( AzureOpenAIConfigBase, OpenAIChatCompletionBase, OpenAITextCompletionBase @@ -55,7 +57,7 @@ class AzureChatCompletion( ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an AzureChatCompletion service. @@ -80,7 +82,7 @@ class AzureChatCompletion( The default value is False. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) logger: deprecated, use 'log' instead. """ @@ -94,7 +96,7 @@ class AzureChatCompletion( ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an AzureChatCompletion service. @@ -117,7 +119,7 @@ class AzureChatCompletion( The default value is False. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) logger: deprecated, use 'log' instead. """ @@ -126,7 +128,7 @@ class AzureChatCompletion( self, deployment_name: str, async_client: AsyncAzureOpenAI, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an AzureChatCompletion service. @@ -138,7 +140,7 @@ class AzureChatCompletion( Resource Management > Deployments in the Azure portal or, alternatively, under Management > Deployments in Azure OpenAI Studio. async_client {AsyncAzureOpenAI} -- An existing client to use. - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) """ @overload @@ -151,7 +153,7 @@ class AzureChatCompletion( ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, use_extensions: bool = False, ) -> None: """ @@ -191,10 +193,10 @@ class AzureChatCompletion( ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, - logger: Optional[Logger] = None, + log: Optional[Any] = None, async_client: Optional[AsyncAzureOpenAI] = None, use_extensions: bool = False, + **kwargs, ) -> None: """ Initialize an AzureChatCompletion service. @@ -223,15 +225,21 @@ class AzureChatCompletion( The default value is False. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log: The logger instance to use. (Optional) - logger: deprecated, use 'log' instead. + log: The logger instance to use. (Optional) (Deprecated) + logger: deprecated. async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) use_extensions: Whether to use extensions, for example when chatting with data. (Optional) When True, base_url is overwritten to '{endpoint}/openai/deployments/{deployment_name}/extensions'. The default value is False. """ - if logger: - logger.warning("The 'logger' argument is deprecated, use 'log' instead.") + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + if kwargs.get("logger"): + logger.warning( + "The 'logger' argument is deprecated. Please use the `logging` module instead." + ) if use_extensions: base_url = f"{str(endpoint).rstrip('/')}/openai/deployments/{deployment_name}/extensions" @@ -249,7 +257,6 @@ class AzureChatCompletion( ad_token=ad_token, ad_token_provider=ad_token_provider, default_headers=default_headers, - log=log or logger, ai_model_type=OpenAIModelTypes.CHAT, async_client=async_client, ) @@ -262,7 +269,7 @@ class AzureChatCompletion( Arguments: settings: A dictionary of settings for the service. should contains keys: deployment_name, endpoint, api_key - and optionally: api_version, ad_auth, default_headers, log + and optionally: api_version, ad_auth, default_headers """ return AzureChatCompletion( deployment_name=settings.get("deployment_name"), @@ -273,14 +280,13 @@ class AzureChatCompletion( ad_token=settings.get("ad_token"), ad_token_provider=settings.get("ad_token_provider"), default_headers=settings.get("default_headers"), - log=settings.get("log"), ) async def complete_chat_with_data_async( self, messages: List[Dict[str, str]], request_settings: "ChatRequestSettings", - logger: Optional[Logger] = None, + logger: Optional[Any] = None, functions: Optional[List[Dict[str, Any]]] = None, ) -> Union[ Tuple[str, str], @@ -293,7 +299,7 @@ class AzureChatCompletion( Arguments: messages {List[Tuple[str,str]]} -- The messages to use for the chat completion. request_settings {ChatRequestSettings} -- The settings to use for the chat completion request. - logger {Optional[Logger]} -- The logger instance to use. (Optional) + logger -- The logger instance to use. (Optional) functions {Optional[List[Dict[str, Any]]]} -- The functions to use for the chat completion. (Optional) Returns: @@ -317,14 +323,14 @@ class AzureChatCompletion( self, messages: List[Dict[str, str]], settings: "ChatRequestSettings", - logger: Optional[Logger] = None, + logger: Optional[Any] = None, ) -> AsyncGenerator[Union[str, List[str]], None]: """Executes a chat completion request with data and returns the result. Arguments: messages {List[Tuple[str,str]]} -- The messages to use for the chat completion. settings {ChatRequestSettings} -- The settings to use for the chat completion request. - logger {Optional[Logger]} -- The logger instance to use. (Optional) + logger -- The logger instance to use. (Optional) Returns: Union[str, List[str]] -- The completion result(s). diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py index 4e7e24768a..d6ba674556 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. import json -from logging import Logger +import logging from typing import Any, Awaitable, Callable, Dict, Mapping, Optional, Union from openai import AsyncAzureOpenAI @@ -19,6 +19,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( from semantic_kernel.connectors.telemetry import APP_INFO from semantic_kernel.sk_pydantic import HttpsUrl +logger: logging.Logger = logging.getLogger(__name__) + class AzureOpenAIConfigBase(OpenAIHandler): """Internal class for configuring a connection to an Azure OpenAI service.""" @@ -35,7 +37,7 @@ class AzureOpenAIConfigBase(OpenAIHandler): ad_token: Optional[str] = None, ad_token_provider: Optional[Callable[[], Union[str, Awaitable[str]]]] = None, default_headers: Union[Mapping[str, str], None] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, async_client: Optional[AsyncAzureOpenAI] = None, ) -> None: """Internal class for configuring a connection to an Azure OpenAI service. @@ -51,12 +53,16 @@ class AzureOpenAIConfigBase(OpenAIHandler): ad_token_provider {Optional[Callable[[], Union[str, Awaitable[str]]]]} -- A callable or coroutine function providing Azure AD tokens. (Optional) default_headers {Union[Mapping[str, str], None]} -- Default headers for HTTP requests. (Optional) - log {Optional[Logger]} -- Logger instance for logging purposes. (Optional) + log -- Logger instance for logging purposes. (Optional) (Deprecated) async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) The `validate_call` decorator is used with a configuration that allows arbitrary types. This is necessary for types like `HttpsUrl` and `OpenAIModelTypes`. """ + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) # Merge APP_INFO into the headers if it exists merged_headers = default_headers.copy() if default_headers else {} if APP_INFO: @@ -95,7 +101,6 @@ class AzureOpenAIConfigBase(OpenAIHandler): super().__init__( ai_model_id=deployment_name, - log=log, client=async_client, ai_model_type=ai_model_type, ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py index ca47029c11..23c96a8311 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py @@ -1,8 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. - -from logging import Logger -from typing import Dict, Mapping, Optional, overload +import logging +from typing import Any, Dict, Mapping, Optional, overload from openai import AsyncAzureOpenAI from openai.lib.azure import AsyncAzureADTokenProvider @@ -18,6 +17,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base OpenAITextCompletionBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): """Azure Text Completion class.""" @@ -31,7 +32,7 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an AzureTextCompletion service. @@ -54,8 +55,8 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): The default value is False. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log: The logger instance to use. (Optional) - logger: deprecated, use 'log' instead. + log: The logger instance to use. (Optional) (Deprecated) + logger: deprecated. """ @overload @@ -68,7 +69,7 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an AzureTextCompletion service. @@ -91,7 +92,7 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): The default value is False. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) logger: deprecated, use 'log' instead. """ @@ -100,7 +101,7 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): self, deployment_name: str, async_client: AsyncAzureOpenAI, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an AzureChatCompletion service. @@ -112,7 +113,7 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): Resource Management > Deployments in the Azure portal or, alternatively, under Management > Deployments in Azure OpenAI Studio. async_client {AsyncAzureOpenAI} -- An existing client to use. - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) """ def __init__( @@ -125,9 +126,9 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, - logger: Optional[Logger] = None, + log: Optional[Any] = None, async_client: Optional[AsyncAzureOpenAI] = None, + **kwargs, ) -> None: """ Initialize an AzureTextCompletion service. @@ -150,12 +151,16 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): The default value is False. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) logger: deprecated, use 'log' instead. async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. """ - if logger: - logger.warning("The 'logger' argument is deprecated, use 'log' instead.") + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + if kwargs.get("logger"): + logger.warning("The 'logger' argument is deprecated.") super().__init__( deployment_name=deployment_name, endpoint=endpoint, @@ -165,7 +170,6 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): ad_token=ad_token, ad_token_provider=ad_token_provider, default_headers=default_headers, - log=log or logger, ai_model_type=OpenAIModelTypes.TEXT, async_client=async_client, ) @@ -178,7 +182,7 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): Arguments: settings: A dictionary of settings for the service. should contains keys: deployment_name, endpoint, api_key - and optionally: api_version, ad_auth, log + and optionally: api_version, ad_auth """ return AzureTextCompletion( @@ -190,5 +194,4 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): ad_token=settings.get("ad_token"), ad_token_provider=settings.get("ad_token_provider"), default_headers=settings.get("default_headers"), - log=settings.get("log"), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py index 82cf919527..d8b4d911ba 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import Dict, Mapping, Optional, overload +import logging +from typing import Any, Dict, Mapping, Optional, overload from openai import AsyncAzureOpenAI from openai.lib.azure import AsyncAzureADTokenProvider @@ -18,6 +18,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base OpenAITextEmbeddingBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): """Azure Text Embedding class.""" @@ -27,7 +29,7 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): self, deployment_name: str, async_client: AsyncAzureOpenAI, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an AzureChatCompletion service. @@ -39,7 +41,7 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): Resource Management > Deployments in the Azure portal or, alternatively, under Management > Deployments in Azure OpenAI Studio. async_client {AsyncAzureOpenAI} -- An existing client to use. - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) """ def __init__( @@ -51,9 +53,9 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): ad_token: Optional[str] = None, ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, - logger: Optional[Logger] = None, + log: Optional[Any] = None, async_client: Optional[AsyncAzureOpenAI] = None, + **kwargs, ) -> None: """ Initialize an AzureTextEmbedding service. @@ -79,13 +81,17 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): (Optional) The default value is False. :param default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - :param log: The logger instance to use. (Optional) + :param log: The logger instance to use. (Optional) (Deprecated) :param logger: Deprecated, please use log instead. (Optional) :param async_client: An existing client to use. (Optional) """ - if logger: - logger.warning("The 'logger' argument is deprecated, use 'log' instead.") + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + if kwargs.get("logger"): + logger.warning("The 'logger' argument is deprecated.") super().__init__( deployment_name=deployment_name, endpoint=endpoint, @@ -94,7 +100,6 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): ad_token=ad_token, ad_token_provider=ad_token_provider, default_headers=default_headers, - log=log or logger, ai_model_type=OpenAIModelTypes.EMBEDDING, async_client=async_client, ) @@ -107,7 +112,7 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): Arguments: settings: A dictionary of settings for the service. should contains keys: deployment_name, endpoint, api_key - and optionally: api_version, ad_auth, log + and optionally: api_version, ad_auth """ return AzureTextEmbedding( deployment_name=settings["deployment_name"], @@ -117,5 +122,4 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): ad_token=settings.get("ad_token"), ad_token_provider=settings.get("ad_token_provider"), default_headers=settings.get("default_headers"), - log=settings.get("log"), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py index aea0228fee..d0ff3546b0 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import Dict, Mapping, Optional, overload +import logging +from typing import Any, Dict, Mapping, Optional, overload from openai import AsyncOpenAI @@ -18,6 +18,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base OpenAITextCompletionBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class OpenAIChatCompletion( OpenAIConfigBase, OpenAIChatCompletionBase, OpenAITextCompletionBase @@ -29,7 +31,7 @@ class OpenAIChatCompletion( self, ai_model_id: str, async_client: AsyncOpenAI, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAIChatCompletion service. @@ -38,7 +40,7 @@ class OpenAIChatCompletion( ai_model_id {str} -- OpenAI model name, see https://platform.openai.com/docs/models async_client {AsyncOpenAI} -- An existing client to use. - log: The logger instance to use. (Optional) + log: The logger instance to use. (Optional) (Deprecated) """ @overload @@ -48,7 +50,7 @@ class OpenAIChatCompletion( api_key: Optional[str] = None, org_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAIChatCompletion service. @@ -63,7 +65,7 @@ class OpenAIChatCompletion( account belongs to multiple organizations. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) + log -- The logger instance to use. (Optional) (Deprecated) """ @overload @@ -72,7 +74,7 @@ class OpenAIChatCompletion( ai_model_id: str, api_key: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAIChatCompletion service. @@ -84,7 +86,7 @@ class OpenAIChatCompletion( https://platform.openai.com/account/api-keys default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) + log -- The logger instance to use. (Optional) (Deprecated) """ def __init__( @@ -93,8 +95,8 @@ class OpenAIChatCompletion( api_key: Optional[str] = None, org_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, async_client: Optional[AsyncOpenAI] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAIChatCompletion service. @@ -109,16 +111,19 @@ class OpenAIChatCompletion( account belongs to multiple organizations. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) + log -- The logger instance to use. (Optional) (Deprecated) """ + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) super().__init__( ai_model_id=ai_model_id, api_key=api_key, org_id=org_id, ai_model_type=OpenAIModelTypes.CHAT, default_headers=default_headers, - log=log, async_client=async_client, ) @@ -136,5 +141,4 @@ class OpenAIChatCompletion( api_key=settings["api_key"], org_id=settings.get("org_id"), default_headers=settings.get("default_headers"), - log=settings.get("log"), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py index 86ae7acbdf..377a9d2388 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import ( TYPE_CHECKING, Any, @@ -22,24 +22,29 @@ from semantic_kernel.connectors.ai.open_ai.utils import _parse_message if TYPE_CHECKING: from semantic_kernel.connectors.ai.chat_request_settings import ChatRequestSettings +logger: logging.Logger = logging.getLogger(__name__) + class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase): async def complete_chat_async( self, messages: List[Dict[str, str]], settings: "ChatRequestSettings", - logger: Optional[Logger] = None, + **kwargs, ) -> Union[str, List[str]]: """Executes a chat completion request and returns the result. Arguments: messages {List[Tuple[str,str]]} -- The messages to use for the chat completion. settings {ChatRequestSettings} -- The settings to use for the chat completion request. - logger {Optional[Logger]} -- The logger instance to use. (Optional) Returns: Union[str, List[str]] -- The completion result(s). """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) response = await self._send_request( messages=messages, request_settings=settings, stream=False ) @@ -54,7 +59,7 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase): messages: List[Dict[str, str]], functions: List[Dict[str, Any]], request_settings: "ChatRequestSettings", - logger: Optional[Logger] = None, + **kwargs, ) -> Union[ Tuple[Optional[str], Optional[FunctionCall]], List[Tuple[Optional[str], Optional[FunctionCall]]], @@ -65,12 +70,14 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase): messages {List[Tuple[str,str]]} -- The messages to use for the chat completion. functions {List[Dict[str, Any]]} -- The functions to use for the chat completion. settings {ChatRequestSettings} -- The settings to use for the chat completion request. - logger {Optional[Logger]} -- The logger instance to use. (Optional) Returns: Union[str, List[str]] -- The completion result(s). """ - + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) response = await self._send_request( messages=messages, request_settings=request_settings, @@ -79,28 +86,29 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase): ) if len(response.choices) == 1: - return _parse_message(response.choices[0].message, self.log) + return _parse_message(response.choices[0].message) else: - return [ - _parse_message(choice.message, self.log) for choice in response.choices - ] + return [_parse_message(choice.message) for choice in response.choices] async def complete_chat_stream_async( self, messages: List[Dict[str, str]], settings: "ChatRequestSettings", - logger: Optional[Logger] = None, + **kwargs, ) -> AsyncGenerator[Union[str, List[str]], None]: """Executes a chat completion request and returns the result. Arguments: messages {List[Tuple[str,str]]} -- The messages to use for the chat completion. settings {ChatRequestSettings} -- The settings to use for the chat completion request. - logger {Optional[Logger]} -- The logger instance to use. (Optional) Returns: Union[str, List[str]] -- The completion result(s). """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) response = await self._send_request( messages=messages, request_settings=settings, stream=True ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py index 0ce2f18e2f..62845aa374 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. import json -from logging import Logger +import logging from typing import Any, Dict, Mapping, Optional from openai import AsyncOpenAI @@ -19,6 +19,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_model_types import ( ) from semantic_kernel.connectors.telemetry import APP_INFO +logger: logging.Logger = logging.getLogger(__name__) + class OpenAIConfigBase(OpenAIHandler): @validate_call(config=dict(arbitrary_types_allowed=True)) @@ -30,7 +32,7 @@ class OpenAIConfigBase(OpenAIHandler): org_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, async_client: Optional[AsyncOpenAI] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """Initialize a client for OpenAI services. @@ -48,10 +50,12 @@ class OpenAIConfigBase(OpenAIHandler): unless the account belongs to multiple organizations. default_headers {Optional[Mapping[str, str]]} -- Default headers for HTTP requests. (Optional) - log {Optional[Logger]} -- Logger instance for logging purposes. (Optional) """ - + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) # Merge APP_INFO into the headers if it exists merged_headers = default_headers.copy() if default_headers else {} if APP_INFO: @@ -72,7 +76,6 @@ class OpenAIConfigBase(OpenAIHandler): super().__init__( ai_model_id=ai_model_id, client=async_client, - log=log, ai_model_type=ai_model_type, ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py index 9f30afcf97..c78287041b 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. +import logging from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Union @@ -19,6 +20,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_model_types import ( OpenAIModelTypes, ) +logger: logging.Logger = logging.getLogger(__name__) + class OpenAIHandler(AIServiceClientBase, ABC): """Internal class for calls to OpenAI API's.""" @@ -78,7 +81,7 @@ class OpenAIHandler(AIServiceClientBase, ABC): ex, ) from ex if not isinstance(response, AsyncStream): - self.log.info(f"OpenAI usage: {response.usage}") + logger.info(f"OpenAI usage: {response.usage}") self.prompt_tokens += response.usage.prompt_tokens self.completion_tokens += response.usage.completion_tokens self.total_tokens += response.usage.total_tokens @@ -185,7 +188,7 @@ class OpenAIHandler(AIServiceClientBase, ABC): # TODO: the openai response is cast to a list[float], could be used instead of nparray raw_embeddings.extend([array(x.embedding) for x in response.data]) if response.usage: - self.log.info(f"OpenAI usage: {response.usage}") + logger.info(f"OpenAI usage: {response.usage}") self.prompt_tokens += response.usage.prompt_tokens self.total_tokens += response.usage.total_tokens return array(raw_embeddings) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py index 9b106dbb07..ebfda6bcea 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import Dict, Mapping, Optional, overload +import logging +from typing import Any, Dict, Mapping, Optional, overload from openai import AsyncOpenAI @@ -15,6 +15,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base OpenAITextCompletionBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): """OpenAI Text Completion class.""" @@ -24,7 +26,7 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): self, ai_model_id: str, async_client: AsyncOpenAI, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAITextCompletion service. @@ -33,7 +35,6 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): ai_model_id {str} -- OpenAI model name, see https://platform.openai.com/docs/models async_client {AsyncOpenAI} -- An existing client to use. - log: The logger instance to use. (Optional) """ @overload @@ -43,7 +44,7 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): api_key: Optional[str] = None, org_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAITextCompletion service. @@ -58,7 +59,6 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): account belongs to multiple organizations. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) """ @overload @@ -67,7 +67,7 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): ai_model_id: str, api_key: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAITextCompletion service. @@ -79,7 +79,6 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): https://platform.openai.com/account/api-keys (Optional) default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) """ def __init__( @@ -88,7 +87,7 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): api_key: Optional[str] = None, org_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, async_client: Optional[AsyncOpenAI] = None, ) -> None: """ @@ -104,14 +103,16 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): account belongs to multiple organizations. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) """ + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) super().__init__( ai_model_id=ai_model_id, api_key=api_key, org_id=org_id, - log=log, ai_model_type=OpenAIModelTypes.TEXT, default_headers=default_headers, async_client=async_client, @@ -131,5 +132,4 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): api_key=settings["api_key"], org_id=settings.get("org_id"), default_headers=settings.get("default_headers"), - log=settings.get("log"), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py index f035d68a43..6c1b8f73e6 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import TYPE_CHECKING, AsyncGenerator, List, Optional, Union +import logging +from typing import TYPE_CHECKING, AsyncGenerator, List, Union from openai.types.completion import Completion @@ -15,24 +15,29 @@ if TYPE_CHECKING: CompleteRequestSettings, ) +logger: logging.Logger = logging.getLogger(__name__) + class OpenAITextCompletionBase(TextCompletionClientBase, OpenAIHandler): async def complete_async( self, prompt: str, settings: "CompleteRequestSettings", - logger: Optional[Logger] = None, + **kwargs, ) -> Union[str, List[str]]: """Executes a completion request and returns the result. Arguments: prompt {str} -- The prompt to use for the completion request. settings {CompleteRequestSettings} -- The settings to use for the completion request. - logger {Optional[Logger]} -- The logger instance to use. (Optional) Returns: Union[str, List[str]] -- The completion result(s). """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) response = await self._send_request( prompt=prompt, request_settings=settings, stream=False ) @@ -49,7 +54,7 @@ class OpenAITextCompletionBase(TextCompletionClientBase, OpenAIHandler): self, prompt: str, settings: "CompleteRequestSettings", - logger: Optional[Logger] = None, + **kwargs, ) -> AsyncGenerator[Union[str, List[str]], None]: """ Executes a completion request and streams the result. @@ -58,11 +63,14 @@ class OpenAITextCompletionBase(TextCompletionClientBase, OpenAIHandler): Arguments: prompt {str} -- The prompt to use for the completion request. settings {CompleteRequestSettings} -- The settings to use for the completion request. - logger {Optional[Logger]} -- The logger instance to use. (Optional) Returns: Union[str, List[str]] -- The completion result(s). """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) response = await self._send_request( prompt=prompt, request_settings=settings, stream=True ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py index 740349d2ed..7f9eef45f6 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import Dict, Mapping, Optional, overload +import logging +from typing import Any, Dict, Mapping, Optional, overload from openai import AsyncOpenAI @@ -15,6 +15,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base OpenAITextEmbeddingBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): """OpenAI Text Embedding class.""" @@ -24,7 +26,7 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): self, ai_model_id: str, async_client: AsyncOpenAI, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: """ Initialize an OpenAITextEmbedding service. @@ -33,7 +35,6 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): ai_model_id {str} -- OpenAI model name, see https://platform.openai.com/docs/models async_client {AsyncOpenAI} -- An existing client to use. - log: The logger instance to use. (Optional) """ def __init__( @@ -42,8 +43,8 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): api_key: Optional[str] = None, org_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Logger] = None, async_client: Optional[AsyncOpenAI] = None, + log: Optional[Any] = None, ) -> None: """ Initializes a new instance of the OpenAITextCompletion class. @@ -58,7 +59,6 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): account belongs to multiple organizations. default_headers {Optional[Mapping[str,str]]}: The default headers mapping of string keys to string values for HTTP requests. (Optional) - log {Optional[Logger]} -- The logger instance to use. (Optional) async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) """ super().__init__( @@ -67,9 +67,12 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): ai_model_type=OpenAIModelTypes.EMBEDDING, org_id=org_id, default_headers=default_headers, - log=log, async_client=async_client, ) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) @classmethod def from_dict(cls, settings: Dict[str, str]) -> "OpenAITextEmbedding": @@ -85,5 +88,4 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): api_key=settings["api_key"], org_id=settings.get("org_id"), default_headers=settings.get("default_headers"), - log=settings.get("log"), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/utils.py b/python/semantic_kernel/connectors/ai/open_ai/utils.py index 909f269c9b..d96b1f3691 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/utils.py +++ b/python/semantic_kernel/connectors/ai/open_ai/utils.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import Any, Dict, List, Optional, Tuple, Union from openai.types.chat import ChatCompletion @@ -12,6 +12,8 @@ from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_promp ) from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +logger: logging.Logger = logging.getLogger(__name__) + def _describe_function(function: SKFunctionBase) -> Dict[str, str]: """Create the object used for function_calling. @@ -101,14 +103,17 @@ def get_function_calling_object( async def execute_function_call( - kernel: Kernel, function_call: FunctionCall, log: Optional[Logger] = None + kernel: Kernel, function_call: FunctionCall, log: Optional[Any] = None ) -> str: + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) result = await kernel.run_async( kernel.func(**function_call.split_name_dict()), input_vars=function_call.to_context_variables(), ) - if log: - log.info(f"Function call result: {result}") + logger.info(f"Function call result: {result}") return str(result) @@ -120,7 +125,7 @@ async def chat_completion_with_function_call( chat_function_name: Optional[str] = None, chat_function: Optional[SKFunctionBase] = None, *, - log: Optional[Logger] = None, + log: Optional[Any] = None, **kwargs: Dict[str, Any], ) -> SKContext: """Perform a chat completion with auto-executing function calling. @@ -144,13 +149,16 @@ async def chat_completion_with_function_call( chat_function: the chat function, if not provided, it will be retrieved from the kernel. make sure to provide either the chat_function or the chat_skill_name and chat_function_name. - log: the logger to use. max_function_calls: the maximum number of function calls to execute, defaults to 5. current_call_count: the current number of function calls executed. returns: the context with the result of the chat completion, just like a regular invoke_async/run_async. """ + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) # check the number of function calls max_function_calls = kwargs.get("max_function_calls", 5) current_call_count = kwargs.get("current_call_count", 0) @@ -171,7 +179,7 @@ async def chat_completion_with_function_call( # if there is no function_call or if the content is not a FunctionCall object, return the context if function_call is None or not isinstance(function_call, FunctionCall): return context - result = await execute_function_call(kernel, function_call, log=log) + result = await execute_function_call(kernel, function_call) # add the result to the chat prompt template chat_function._chat_prompt_template.add_function_response_message( name=function_call.name, content=str(result) @@ -182,14 +190,13 @@ async def chat_completion_with_function_call( chat_function=chat_function, functions=functions, context=context, - log=log, max_function_calls=max_function_calls, current_call_count=current_call_count + 1, ) def _parse_message( - message: ChatCompletion, logger: Optional[Logger] = None, with_data: bool = False + message: ChatCompletion, with_data: bool = False, **kwargs ) -> Union[ Tuple[Optional[str], Optional[FunctionCall]], Tuple[Optional[str], Optional[str], Optional[FunctionCall]], @@ -203,6 +210,10 @@ def _parse_message( Returns: Tuple[Optional[str], Optional[Dict]] -- The parsed message. """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) content = message.content if hasattr(message, "content") else None function_call = message.function_call if hasattr(message, "function_call") else None if function_call: diff --git a/python/semantic_kernel/connectors/ai/text_completion_client_base.py b/python/semantic_kernel/connectors/ai/text_completion_client_base.py index fa4a8ca2ab..7e9c76fe52 100644 --- a/python/semantic_kernel/connectors/ai/text_completion_client_base.py +++ b/python/semantic_kernel/connectors/ai/text_completion_client_base.py @@ -2,8 +2,7 @@ from abc import ABC, abstractmethod -from logging import Logger -from typing import TYPE_CHECKING, AsyncGenerator, List, Optional, Union +from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Union if TYPE_CHECKING: from semantic_kernel.connectors.ai.complete_request_settings import ( @@ -19,7 +18,7 @@ class TextCompletionClientBase(ABC): self, prompt: str, settings: "CompleteRequestSettings", - logger: Optional[Logger] = None, + logger: Optional[Any] = None, ) -> Union[str, List[str]]: """ This is the method that is called from the kernel to get a response from a text-optimized LLM. @@ -27,7 +26,6 @@ class TextCompletionClientBase(ABC): Arguments: prompt {str} -- The prompt to send to the LLM. settings {CompleteRequestSettings} -- Settings for the request. - logger {Logger} -- A logger to use for logging. Returns: Union[str, List[str]] -- A string or list of strings representing the response(s) from the LLM. @@ -38,7 +36,7 @@ class TextCompletionClientBase(ABC): self, prompt: str, settings: "CompleteRequestSettings", - logger: Optional[Logger] = None, + logger: Optional[Any] = None, ) -> AsyncGenerator[Union[str, List[str]], None]: """ This is the method that is called from the kernel to get a stream response from a text-optimized LLM. @@ -46,7 +44,6 @@ class TextCompletionClientBase(ABC): Arguments: prompt {str} -- The prompt to send to the LLM. settings {CompleteRequestSettings} -- Settings for the request. - logger {Logger} -- A logger to use for logging. Yields: A stream representing the response(s) from the LLM. diff --git a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py index d5add2b891..bb65760a1c 100644 --- a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py +++ b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. +import logging import uuid -from logging import Logger from typing import List, Optional, Tuple from azure.core.credentials import AzureKeyCredential, TokenCredential @@ -27,13 +27,13 @@ from semantic_kernel.connectors.memory.azure_cognitive_search.utils import ( ) from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class AzureCognitiveSearchMemoryStore(MemoryStoreBase): _search_index_client: SearchIndexClient = None _vector_size: int = None - _logger: Logger = None def __init__( self, @@ -42,7 +42,7 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase): admin_key: Optional[str] = None, azure_credentials: Optional[AzureKeyCredential] = None, token_credentials: Optional[TokenCredential] = None, - logger: Optional[Logger] = None, + **kwargs, ) -> None: """Initializes a new instance of the AzureCognitiveSearchMemoryStore class. @@ -54,12 +54,15 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase): azure_credentials {Optional[AzureKeyCredential]} -- Azure Cognitive Search credentials (default: {None}). token_credentials {Optional[TokenCredential]} -- Azure Cognitive Search token credentials (default: {None}). - logger {Optional[Logger]} -- The logger to use (default: {None}). Instantiate using Async Context Manager: async with AzureCognitiveSearchMemoryStore(<...>) as memory: await memory.<...> """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) try: pass except ImportError: @@ -68,7 +71,6 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase): "Please install Azure Cognitive Search client" ) - self._logger = logger or NullLogger() self._vector_size = vector_size self._search_index_client = get_search_index_async_client( search_endpoint, admin_key, azure_credentials, token_credentials diff --git a/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py b/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py index 031a70d536..d795849227 100644 --- a/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py +++ b/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import TYPE_CHECKING, List, Optional, Tuple from numpy import array, ndarray @@ -12,23 +12,23 @@ from semantic_kernel.connectors.memory.chroma.utils import ( ) from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger if TYPE_CHECKING: import chromadb import chromadb.config from chromadb.api.models.Collection import Collection +logger: logging.Logger = logging.getLogger(__name__) + class ChromaMemoryStore(MemoryStoreBase): _client: "chromadb.Client" - _logger: Logger def __init__( self, persist_directory: Optional[str] = None, client_settings: Optional["chromadb.config.Settings"] = None, - logger: Optional[Logger] = None, + **kwargs, ) -> None: """ ChromaMemoryStore provides an interface to store and retrieve data using ChromaDB. @@ -62,6 +62,10 @@ class ChromaMemoryStore(MemoryStoreBase): "Please install it with `pip install chromadb`." ) + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if client_settings: self._client_settings = client_settings else: @@ -74,7 +78,6 @@ class ChromaMemoryStore(MemoryStoreBase): self._persist_directory = persist_directory self._default_query_includes = ["embeddings", "metadatas", "documents"] - self._logger = logger or NullLogger() self._default_embedding_function = "DisableChromaEmbeddingFunction" async def create_collection_async(self, collection_name: str) -> None: @@ -285,7 +288,7 @@ class ChromaMemoryStore(MemoryStoreBase): List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. """ if with_embeddings is False: - self._logger.warning( + logger.warning( "Chroma returns distance score not cosine similarity score.\ So embeddings are automatically queried from database for calculation." ) diff --git a/python/semantic_kernel/connectors/memory/chroma/utils.py b/python/semantic_kernel/connectors/memory/chroma/utils.py index fa45441569..51c7ab142e 100644 --- a/python/semantic_kernel/connectors/memory/chroma/utils.py +++ b/python/semantic_kernel/connectors/memory/chroma/utils.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. +import logging from typing import TYPE_CHECKING, List from numpy import array, linalg, ndarray @@ -9,6 +10,8 @@ from semantic_kernel.memory.memory_record import MemoryRecord if TYPE_CHECKING: from chromadb.api.types import QueryResult +logger: logging.Logger = logging.getLogger(__name__) + def camel_to_snake(camel_str): snake_str = "" @@ -83,7 +86,7 @@ def query_results_to_records( def chroma_compute_similarity_scores( - embedding: ndarray, embedding_array: ndarray, logger=None + embedding: ndarray, embedding_array: ndarray, **kwargs ) -> ndarray: """Computes the cosine similarity scores between a query embedding and a group of embeddings. Arguments: @@ -92,6 +95,10 @@ def chroma_compute_similarity_scores( Returns: ndarray -- The cosine similarity scores. """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) query_norm = linalg.norm(embedding) collection_norm = linalg.norm(embedding_array, axis=1) @@ -106,7 +113,7 @@ def chroma_compute_similarity_scores( similarity_scores[valid_indices] = embedding.dot( embedding_array[valid_indices].T ) / (query_norm * collection_norm[valid_indices]) - if not valid_indices.all() and logger: + if not valid_indices.all(): logger.warning( "Some vectors in the embedding collection are zero vectors." "Ignoring cosine similarity score computation for those vectors." diff --git a/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py b/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py index 709fd14ced..09082b3670 100644 --- a/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py +++ b/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import List, Optional, Tuple from numpy import array, expand_dims, ndarray @@ -8,7 +8,8 @@ from pymilvus.milvus_client import milvus_client from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) def memoryrecord_to_milvus_dict(mem: MemoryRecord) -> dict: @@ -65,7 +66,7 @@ class MilvusMemoryStore(MemoryStoreBase): self, uri: str = "http://localhost:19530", token: Optional[str] = None, - logger: Optional[Logger] = None, + **kwargs, ) -> None: """MilvusMemoryStore allows for searching for records using Milvus/Zilliz Cloud. @@ -79,11 +80,13 @@ class MilvusMemoryStore(MemoryStoreBase): "http://localhost:19530". token (Optional[str], optional): The token to connect to the cluster if authentication is required. Defaults to None. - logger (Optional[Logger], optional): Logger to use. Defaults to None. """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self._uri = uri self._token = (token,) - self._logger = logger or NullLogger() self._client = milvus_client.MilvusClient( uri=uri, token=token, @@ -215,9 +218,7 @@ class MilvusMemoryStore(MemoryStoreBase): """ # Check if the collection exists. if collection_name not in self._client.list_collections(): - self._logger.debug( - f"Collection {collection_name} does not exist, cannot insert." - ) + logger.debug(f"Collection {collection_name} does not exist, cannot insert.") raise Exception( f"Collection {collection_name} does not exist, cannot insert." ) @@ -232,7 +233,7 @@ class MilvusMemoryStore(MemoryStoreBase): collection_name=collection_name, data=insert_list, batch_size=batch_size ) except Exception as e: - self._logger.debug(f"Upsert failed due to: {e}") + logger.debug(f"Upsert failed due to: {e}") raise e async def get_async( @@ -272,9 +273,7 @@ class MilvusMemoryStore(MemoryStoreBase): """ # Check if the collection exists if collection_name not in self._client.list_collections(): - self._logger.debug( - f"Collection {collection_name} does not exist, cannot get." - ) + logger.debug(f"Collection {collection_name} does not exist, cannot get.") raise Exception("Collection {collection_name} does not exist, cannot get.") try: gets = self._client.get( @@ -284,7 +283,7 @@ class MilvusMemoryStore(MemoryStoreBase): ) return [milvus_dict_to_memoryrecord(get) for get in gets] except Exception as e: - self._logger.debug(f"Get failed due to: {e}") + logger.debug(f"Get failed due to: {e}") raise e async def remove_async(self, collection_name: str, key: str) -> None: @@ -308,9 +307,7 @@ class MilvusMemoryStore(MemoryStoreBase): e: Failure to remove key. """ if collection_name not in self._client.list_collections(): - self._logger.debug( - f"Collection {collection_name} does not exist, cannot remove." - ) + logger.debug(f"Collection {collection_name} does not exist, cannot remove.") raise Exception( f"Collection {collection_name} does not exist, cannot remove." ) @@ -320,7 +317,7 @@ class MilvusMemoryStore(MemoryStoreBase): pks=keys, ) except Exception as e: - self._logger.debug(f"Remove failed due to: {e}") + logger.debug(f"Remove failed due to: {e}") raise e def _search(self, collection_name, data, limit, distance_metric): @@ -342,7 +339,7 @@ class MilvusMemoryStore(MemoryStoreBase): )[0] return results, distance_metric except Exception as e: - self._logger.debug(f"Search failed with IP, testing L2: {e}") + logger.debug(f"Search failed with IP, testing L2: {e}") try: distance_metric = distance_pairs[distance_metric.lower()] results = self._client.search( @@ -354,7 +351,7 @@ class MilvusMemoryStore(MemoryStoreBase): )[0] return results, distance_metric except Exception as e: - self._logger.debug(f"Search failed with L2: {e}") + logger.debug(f"Search failed with L2: {e}") raise e async def get_nearest_matches_async( @@ -383,9 +380,7 @@ class MilvusMemoryStore(MemoryStoreBase): """ # Check if collection exists if collection_name not in self._client.list_collections(): - self._logger.debug( - f"Collection {collection_name} does not exist, cannot search." - ) + logger.debug(f"Collection {collection_name} does not exist, cannot search.") raise Exception( f"Collection {collection_name} does not exist, cannot search." ) @@ -425,7 +420,7 @@ class MilvusMemoryStore(MemoryStoreBase): output_fields=[EMBEDDING_FIELD], ) except Exception as e: - self._logger.debug(f"Get embeddings in search failed due to: {e}.") + logger.debug(f"Get embeddings in search failed due to: {e}.") raise e vectors = {res[ID_FIELD]: res[EMBEDDING_FIELD] for res in vectors} diff --git a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py index b760e26092..1301de2d21 100644 --- a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py +++ b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. from __future__ import annotations -from logging import Logger +import logging from typing import Any, List, Mapping, Optional, Tuple from motor import MotorCommandCursor, core, motor_asyncio @@ -19,17 +19,17 @@ from semantic_kernel.connectors.memory.mongodb_atlas.utils import ( ) from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger from semantic_kernel.utils.settings import mongodb_atlas_settings_from_dot_env +logger: logging.Logger = logging.getLogger(__name__) + class MongoDBAtlasMemoryStore(MemoryStoreBase): """Memory Store for MongoDB Atlas Vector Search Connections""" - __slots__ = ("_mongo_client", "_logger", "__database_name") + __slots__ = ("_mongo_client", "__database_name") _mongo_client: motor_asyncio.AsyncIOMotorClient - _logger: Logger __database_name: str __index_name: str @@ -38,14 +38,17 @@ class MongoDBAtlasMemoryStore(MemoryStoreBase): index_name: Optional[str] = None, connection_string: Optional[str] = None, database_name: Optional[str] = None, - logger: Optional[Logger] = None, read_preference: Optional[ReadPreference] = ReadPreference.PRIMARY, + **kwargs, ): + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self._mongo_client = motor_asyncio.AsyncIOMotorClient( connection_string or mongodb_atlas_settings_from_dot_env(), read_preference=read_preference, ) - self._logger = logger or NullLogger() self.__database_name = database_name or DEFAULT_DB_NAME self.__index_name = index_name or DEFAULT_SEARCH_INDEX_NAME @@ -161,7 +164,7 @@ class MongoDBAtlasMemoryStore(MemoryStoreBase): ].bulk_write(upserts, ordered=False) # Assert the number matched and the number upserted equal the total batch updated - self._logger.debug( + logger.debug( "matched_count=%s, upserted_count=%s", bulk_update_result.matched_count, bulk_update_result.upserted_count, @@ -241,7 +244,7 @@ class MongoDBAtlasMemoryStore(MemoryStoreBase): bulk_write_result = await self.database[collection_name].bulk_write( deletes, ordered=False ) - self._logger.debug("%s entries deleted", bulk_write_result.deleted_count) + logger.debug("%s entries deleted", bulk_write_result.deleted_count) async def get_nearest_matches_async( self, diff --git a/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py b/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py index aeae07244b..334a79347b 100644 --- a/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py +++ b/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import List, Optional, Tuple import pinecone @@ -13,7 +13,6 @@ from semantic_kernel.connectors.memory.pinecone.utils import ( ) from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger # Limitations set by Pinecone at https://docs.pinecone.io/docs/limits MAX_DIMENSIONALITY = 20000 @@ -23,11 +22,12 @@ MAX_QUERY_WITH_METADATA_BATCH_SIZE = 1000 MAX_FETCH_BATCH_SIZE = 1000 MAX_DELETE_BATCH_SIZE = 1000 +logger: logging.Logger = logging.getLogger(__name__) + class PineconeMemoryStore(MemoryStoreBase): """A memory store that uses Pinecone as the backend.""" - _logger: Logger _pinecone_api_key: str _pinecone_environment: str _default_dimensionality: int @@ -37,7 +37,7 @@ class PineconeMemoryStore(MemoryStoreBase): api_key: str, environment: str, default_dimensionality: int, - logger: Optional[Logger] = None, + **kwargs, ) -> None: """Initializes a new instance of the PineconeMemoryStore class. @@ -45,8 +45,11 @@ class PineconeMemoryStore(MemoryStoreBase): pinecone_api_key {str} -- The Pinecone API key. pinecone_environment {str} -- The Pinecone environment. default_dimensionality {int} -- The default dimensionality to use for new collections. - logger {Optional[Logger]} -- The logger to use. (default: {None}) """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if default_dimensionality > MAX_DIMENSIONALITY: raise ValueError( f"Dimensionality of {default_dimensionality} exceeds " @@ -55,7 +58,6 @@ class PineconeMemoryStore(MemoryStoreBase): self._pinecone_api_key = api_key self._pinecone_environment = environment self._default_dimensionality = default_dimensionality - self._logger = logger or NullLogger() pinecone.init( api_key=self._pinecone_api_key, environment=self._pinecone_environment diff --git a/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py b/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py index de030c5570..06c52dfb45 100644 --- a/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py +++ b/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py @@ -2,7 +2,7 @@ import atexit import json -from logging import Logger +import logging from typing import List, Optional, Tuple import numpy as np @@ -13,12 +13,13 @@ from psycopg_pool import ConnectionPool from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger # Limitation based on pgvector documentation https://github.com/pgvector/pgvector#what-if-i-want-to-index-vectors-with-more-than-2000-dimensions MAX_DIMENSIONALITY = 2000 DEFAULT_SCHEMA = "public" +logger: logging.Logger = logging.getLogger(__name__) + class PostgresMemoryStore(MemoryStoreBase): """A memory store that uses Postgres with pgvector as the backend.""" @@ -27,7 +28,6 @@ class PostgresMemoryStore(MemoryStoreBase): _connection_pool: ConnectionPool _default_dimensionality: int _schema: str - _logger: Logger def __init__( self, @@ -36,7 +36,7 @@ class PostgresMemoryStore(MemoryStoreBase): min_pool: int, max_pool: int, schema: str = DEFAULT_SCHEMA, - logger: Optional[Logger] = None, + **kwargs, ) -> None: """Initializes a new instance of the PostgresMemoryStore class. @@ -48,9 +48,11 @@ class PostgresMemoryStore(MemoryStoreBase): schema {str} -- The schema to use. (default: {"public"})\n timezone_offset {Optional[str]} -- The timezone offset to use. (default: {None}) Expected format '-7:00'. Uses the local timezone offset when not provided.\n - logger {Optional[Logger]} -- The logger to use. (default: {None}) """ - + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self._check_dimensionality(default_dimensionality) self._connection_string = connection_string @@ -60,7 +62,6 @@ class PostgresMemoryStore(MemoryStoreBase): ) self._schema = schema atexit.register(self._connection_pool.close) - self._logger = logger or NullLogger() async def create_collection_async( self, diff --git a/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py b/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py index 4e3d209f70..3b26654fd1 100644 --- a/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py +++ b/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py @@ -5,8 +5,8 @@ QdrantMemoryStore provides functionality to add Qdrant vector database to suppor The QdrantMemoryStore inherits from MemoryStoreBase for persisting/retrieving data from a Qdrant Vector Database. """ import asyncio +import logging import uuid -from logging import Logger from typing import List, Optional, Tuple from numpy import ndarray @@ -15,26 +15,26 @@ from qdrant_client import models as qdrant_models from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class QdrantMemoryStore(MemoryStoreBase): _qdrantclient: QdrantClient - _logger: Logger def __init__( self, vector_size: int, url: Optional[str] = None, port: Optional[int] = 6333, - logger: Optional[Logger] = None, local: Optional[bool] = False, + **kwargs, ) -> None: - """Initializes a new instance of the QdrantMemoryStore class. - - Arguments: - logger {Optional[Logger]} -- The logger to use. (default: {None}) - """ + """Initializes a new instance of the QdrantMemoryStore class.""" + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if local: if url: self._qdrantclient = QdrantClient(location=url) @@ -43,7 +43,6 @@ class QdrantMemoryStore(MemoryStoreBase): else: self._qdrantclient = QdrantClient(url=url, port=port) - self._logger = logger or NullLogger() self._default_vector_size = vector_size async def create_collection_async(self, collection_name: str) -> None: diff --git a/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py b/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py index d1052419d1..eea40bc388 100644 --- a/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py +++ b/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import List, Optional, Tuple +import logging +from typing import List, Tuple import numpy as np import redis @@ -19,7 +19,8 @@ from semantic_kernel.connectors.memory.redis.utils import ( ) from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class RedisMemoryStore(MemoryStoreBase): @@ -27,7 +28,6 @@ class RedisMemoryStore(MemoryStoreBase): _database: "redis.Redis" _ft: "redis.Redis.ft" - _logger: Logger # Without RedisAI, it is currently not possible to retrieve index-specific vector attributes to have # fully independent collections. _query_dialect: int @@ -45,7 +45,7 @@ class RedisMemoryStore(MemoryStoreBase): vector_type: str = "FLOAT32", vector_index_algorithm: str = "HNSW", query_dialect: int = 2, - logger: Optional[Logger] = None, + **kwargs, ) -> None: """ RedisMemoryStore is an abstracted interface to interact with a Redis node connection. @@ -59,15 +59,17 @@ class RedisMemoryStore(MemoryStoreBase): vector_type {str} -- Vector type, defaults to FLOAT32 vector_index_algorithm {str} -- Indexing algorithm for vectors, defaults to HNSW query_dialect {int} -- Query dialect, must be 2 or greater for vector similarity searching, defaults to 2 - logger {Optional[Logger]} -- Logger, defaults to None """ + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if vector_size <= 0: raise ValueError("Vector dimension must be a positive integer") self._database = redis.Redis.from_url(connection_string) self._ft = self._database.ft - self._logger = logger or NullLogger() self._query_dialect = query_dialect self._vector_distance_metric = vector_distance_metric @@ -80,7 +82,7 @@ class RedisMemoryStore(MemoryStoreBase): """ Closes the Redis database connection """ - self._logger.info("Closing Redis connection") + logger.info("Closing Redis connection") self._database.close() async def create_collection_async(self, collection_name: str) -> None: @@ -94,7 +96,7 @@ class RedisMemoryStore(MemoryStoreBase): """ if await self.does_collection_exist_async(collection_name): - self._logger.info(f'Collection "{collection_name}" already exists.') + logger.info(f'Collection "{collection_name}" already exists.') else: index_def = IndexDefinition( prefix=f"{collection_name}:", index_type=IndexType.HASH @@ -119,7 +121,7 @@ class RedisMemoryStore(MemoryStoreBase): definition=index_def, fields=schema ) except Exception as e: - self._logger.error(e) + logger.error(e) raise e async def get_collections_async(self) -> List[str]: @@ -181,7 +183,7 @@ class RedisMemoryStore(MemoryStoreBase): """ if not await self.does_collection_exist_async(collection_name): - self._logger.error(f'Collection "{collection_name}" does not exist') + logger.error(f'Collection "{collection_name}" does not exist') raise Exception(f'Collection "{collection_name}" does not exist') # Typical Redis key structure: collection_name:{some identifier} @@ -196,7 +198,7 @@ class RedisMemoryStore(MemoryStoreBase): ) return record._key except Exception as e: - self._logger.error(e) + logger.error(e) raise e async def upsert_batch_async( @@ -241,7 +243,7 @@ class RedisMemoryStore(MemoryStoreBase): """ if not await self.does_collection_exist_async(collection_name): - self._logger.error(f'Collection "{collection_name}" does not exist') + logger.error(f'Collection "{collection_name}" does not exist') raise Exception(f'Collection "{collection_name}" does not exist') internal_key = get_redis_key(collection_name, key) @@ -289,7 +291,7 @@ class RedisMemoryStore(MemoryStoreBase): key {str} -- ID associated with the memory to remove """ if not await self.does_collection_exist_async(collection_name): - self._logger.error(f'Collection "{collection_name}" does not exist') + logger.error(f'Collection "{collection_name}" does not exist') raise Exception(f'Collection "{collection_name}" does not exist') self._database.delete(get_redis_key(collection_name, key)) @@ -303,7 +305,7 @@ class RedisMemoryStore(MemoryStoreBase): keys {List[str]} -- IDs associated with the memory records to remove """ if not await self.does_collection_exist_async(collection_name): - self._logger.error(f'Collection "{collection_name}" does not exist') + logger.error(f'Collection "{collection_name}" does not exist') raise Exception(f'Collection "{collection_name}" does not exist') self._database.delete(*[get_redis_key(collection_name, key) for key in keys]) @@ -331,7 +333,7 @@ class RedisMemoryStore(MemoryStoreBase): order, or an empty list if no relevant matches are found """ if not await self.does_collection_exist_async(collection_name): - self._logger.error(f'Collection "{collection_name}" does not exist') + logger.error(f'Collection "{collection_name}" does not exist') raise Exception(f'Collection "{collection_name}" does not exist') # Perform a k-nearest neighbors query, score by similarity diff --git a/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py b/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py index 997b0427c8..9d0a730eab 100644 --- a/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py +++ b/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py @@ -1,10 +1,10 @@ # Copyright (c) Microsoft. All rights reserved. import itertools +import logging import os from dataclasses import dataclass from enum import Enum -from logging import Logger from pathlib import Path from typing import Dict, List, Optional, Tuple, Union @@ -24,7 +24,8 @@ from usearch.index import ( from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) @dataclass @@ -126,7 +127,7 @@ class USearchMemoryStore(MemoryStoreBase): def __init__( self, persist_directory: Optional[os.PathLike] = None, - logger: Optional[Logger] = None, + **kwargs, ) -> None: """ Create a USearchMemoryStore instance. @@ -142,9 +143,11 @@ class USearchMemoryStore(MemoryStoreBase): Args: persist_directory (Optional[os.PathLike], default=None): Directory for loading and saving collections. If None, collections are not loaded nor saved. - logger (Optional[Logger], default=None): Logger for diagnostics. If None, a NullLogger is used. """ - self._logger = logger or NullLogger() + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self._persist_directory = ( Path(persist_directory) if persist_directory is not None else None ) diff --git a/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py b/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py index 891fe223a0..344f121142 100644 --- a/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py +++ b/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py @@ -1,9 +1,9 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio +import logging from dataclasses import dataclass -from logging import Logger -from typing import List, Optional, Tuple +from typing import List, Tuple import numpy as np import weaviate @@ -11,7 +11,8 @@ from weaviate.embedded import EmbeddedOptions from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) SCHEMA = { "class": "MemoryRecord", @@ -115,8 +116,11 @@ class WeaviateMemoryStore(MemoryStoreBase): """ return {key.lstrip("_"): value for key, value in sk_dict.items()} - def __init__(self, config: WeaviateConfig, logger: Optional[Logger] = None): - self._logger = logger or NullLogger() + def __init__(self, config: WeaviateConfig, **kwargs): + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self.config = config self.client = self._initialize_client() diff --git a/python/semantic_kernel/connectors/openapi/sk_openapi.py b/python/semantic_kernel/connectors/openapi/sk_openapi.py index a881284d5d..fba005a8d5 100644 --- a/python/semantic_kernel/connectors/openapi/sk_openapi.py +++ b/python/semantic_kernel/connectors/openapi/sk_openapi.py @@ -17,7 +17,8 @@ from semantic_kernel.connectors.ai.open_ai.const import ( from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT from semantic_kernel.orchestration.sk_function_base import SKFunctionBase from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class PreparedRestApiRequest: @@ -40,7 +41,11 @@ class PreparedRestApiRequest: f"request_body={self.request_body})" ) - def validate_request(self, spec: Spec, logger: logging.Logger = NullLogger()): + def validate_request(self, spec: Spec, **kwargs): + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) request = requests.Request( self.method, self.url, @@ -159,8 +164,11 @@ class RestApiOperation: class OpenApiParser: - def __init__(self, logger: logging.Logger = NullLogger()): - self.logger = logger + def __init__(self, **kwargs): + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) """ Import an OpenAPI file. @@ -214,9 +222,7 @@ class OpenApiRunner: def __init__( self, parsed_openapi_document: Mapping[str, str], - logger: logging.Logger = NullLogger(), ): - self.logger = logger self.spec = Spec.from_dict(parsed_openapi_document) async def run_operation( @@ -233,7 +239,7 @@ class OpenApiRunner: headers=headers, request_body=request_body, ) - is_valid = prepared_request.validate_request(spec=self.spec, logger=self.logger) + is_valid = prepared_request.validate_request(spec=self.spec) if not is_valid: return None @@ -262,12 +268,10 @@ def register_openapi_skill( skill_name: str, openapi_document: str, ) -> Dict[str, SKFunctionBase]: - parser = OpenApiParser(logger=kernel.logger) + parser = OpenApiParser() parsed_doc = parser.parse(openapi_document) operations = parser.create_rest_api_operations(parsed_doc) - openapi_runner = OpenApiRunner( - parsed_openapi_document=parsed_doc, logger=kernel.logger - ) + openapi_runner = OpenApiRunner(parsed_openapi_document=parsed_doc) skill = {} @@ -310,8 +314,6 @@ def register_openapi_skill( return run_openapi_operation for operation_id, operation in operations.items(): - kernel.logger.info( - f"Registering OpenAPI operation: {skill_name}.{operation_id}" - ) + logger.info(f"Registering OpenAPI operation: {skill_name}.{operation_id}") skill[operation_id] = create_run_operation_function(openapi_runner, operation) return kernel.import_skill(skill, skill_name) diff --git a/python/semantic_kernel/connectors/search_engine/bing_connector.py b/python/semantic_kernel/connectors/search_engine/bing_connector.py index 2e03a03f8c..2eaed24c79 100644 --- a/python/semantic_kernel/connectors/search_engine/bing_connector.py +++ b/python/semantic_kernel/connectors/search_engine/bing_connector.py @@ -1,13 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. +import logging import urllib -from logging import Logger -from typing import List, Optional +from typing import List import aiohttp from semantic_kernel.connectors.search_engine.connector import ConnectorBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class BingConnector(ConnectorBase): @@ -17,9 +18,12 @@ class BingConnector(ConnectorBase): _api_key: str - def __init__(self, api_key: str, logger: Optional[Logger] = None) -> None: + def __init__(self, api_key: str, **kwargs) -> None: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self._api_key = api_key - self._logger = logger if logger else NullLogger() if not self._api_key: raise ValueError( @@ -57,7 +61,7 @@ class BingConnector(ConnectorBase): if offset < 0: raise ValueError("offset must be greater than 0.") - self._logger.info( + logger.info( f"Received request for bing web search with \ params:\nquery: {query}\nnum_results: {num_results}\noffset: {offset}" ) @@ -65,7 +69,7 @@ class BingConnector(ConnectorBase): _base_url = "https://api.bing.microsoft.com/v7.0/search" _request_url = f"{_base_url}?q={urllib.parse.quote_plus(query)}&count={num_results}&offset={offset}" - self._logger.info(f"Sending GET request to {_request_url}") + logger.info(f"Sending GET request to {_request_url}") headers = {"Ocp-Apim-Subscription-Key": self._api_key} @@ -76,9 +80,9 @@ class BingConnector(ConnectorBase): if response.status == 200: data = await response.json() pages = data["webPages"]["value"] - self._logger.info(pages) + logger.info(pages) result = list(map(lambda x: x["snippet"], pages)) - self._logger.info(result) + logger.info(result) return result else: return [] diff --git a/python/semantic_kernel/connectors/search_engine/google_connector.py b/python/semantic_kernel/connectors/search_engine/google_connector.py index 771df6622a..2997e4053f 100644 --- a/python/semantic_kernel/connectors/search_engine/google_connector.py +++ b/python/semantic_kernel/connectors/search_engine/google_connector.py @@ -1,13 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. +import logging import urllib -from logging import Logger -from typing import List, Optional +from typing import List import aiohttp from semantic_kernel.connectors.search_engine.connector import ConnectorBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class GoogleConnector(ConnectorBase): @@ -17,14 +18,14 @@ class GoogleConnector(ConnectorBase): _api_key: str _search_engine_id: str - _logger: Logger - def __init__( - self, api_key: str, search_engine_id: str, logger: Optional[Logger] = None - ) -> None: + def __init__(self, api_key: str, search_engine_id: str, **kwargs) -> None: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self._api_key = api_key self._search_engine_id = search_engine_id - self._logger = logger if logger else NullLogger() if not self._api_key: raise ValueError("Google Custom Search API key cannot be null.") @@ -63,7 +64,7 @@ class GoogleConnector(ConnectorBase): if offset < 0: raise ValueError("offset must be greater than 0.") - self._logger.info( + logger.info( f"Received request for google search with \ params:\nquery: {query}\nnum_results: {num_results}\noffset: {offset}" ) @@ -75,19 +76,19 @@ class GoogleConnector(ConnectorBase): f"&num={num_results}&start={offset}" ) - self._logger.info("Sending GET request to Google Search API.") + logger.info("Sending GET request to Google Search API.") async with aiohttp.ClientSession() as session: async with session.get(_request_url, raise_for_status=True) as response: if response.status == 200: data = await response.json() - self._logger.info("Request successful.") - self._logger.info(f"API Response: {data}") + logger.info("Request successful.") + logger.info(f"API Response: {data}") items = data["items"] result = [x["snippet"] for x in items] return result else: - self._logger.error( + logger.error( f"Request to Google Search API failed with status code: {response.status}." ) return [] diff --git a/python/semantic_kernel/core_skills/text_memory_skill.py b/python/semantic_kernel/core_skills/text_memory_skill.py index 8657f79904..8f064ca7e4 100644 --- a/python/semantic_kernel/core_skills/text_memory_skill.py +++ b/python/semantic_kernel/core_skills/text_memory_skill.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. import json +import logging import typing as t from typing import ClassVar @@ -9,6 +10,8 @@ from semantic_kernel.skill_definition import sk_function, sk_function_context_pa if t.TYPE_CHECKING: from semantic_kernel.orchestration.sk_context import SKContext +logger: logging.Logger = logging.getLogger(__name__) + class TextMemorySkill(SKBaseModel): COLLECTION_PARAM: ClassVar[str] = "collection" @@ -90,8 +93,7 @@ class TextMemorySkill(SKBaseModel): min_relevance_score=float(relevance), ) if results is None or len(results) == 0: - if context.log is not None: - context.log.warning(f"Memory not found in collection: {collection}") + logger.warning(f"Memory not found in collection: {collection}") return "" return results[0].text if limit == 1 else json.dumps([r.text for r in results]) diff --git a/python/semantic_kernel/kernel.py b/python/semantic_kernel/kernel.py index 1a3fd40cf5..1fe20a3bc7 100644 --- a/python/semantic_kernel/kernel.py +++ b/python/semantic_kernel/kernel.py @@ -3,8 +3,8 @@ import glob import importlib import inspect +import logging import os -from logging import Logger from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union from uuid import uuid4 @@ -53,14 +53,14 @@ from semantic_kernel.template_engine.prompt_template_engine import PromptTemplat from semantic_kernel.template_engine.protocols.prompt_templating_engine import ( PromptTemplatingEngine, ) -from semantic_kernel.utils.null_logger import NullLogger from semantic_kernel.utils.validation import validate_function_name, validate_skill_name T = TypeVar("T") +logger: logging.Logger = logging.getLogger(__name__) + class Kernel: - _log: Logger _skill_collection: SkillCollectionBase _prompt_template_engine: PromptTemplatingEngine _memory: SemanticTextMemoryBase @@ -70,16 +70,17 @@ class Kernel: skill_collection: Optional[SkillCollectionBase] = None, prompt_template_engine: Optional[PromptTemplatingEngine] = None, memory: Optional[SemanticTextMemoryBase] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: - self._log = log if log else NullLogger() + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) self._skill_collection = ( - skill_collection if skill_collection else SkillCollection(self._log) + skill_collection if skill_collection else SkillCollection() ) self._prompt_template_engine = ( - prompt_template_engine - if prompt_template_engine - else PromptTemplateEngine(self._log) + prompt_template_engine if prompt_template_engine else PromptTemplateEngine() ) self._memory = memory if memory else NullMemory() @@ -102,10 +103,6 @@ class Kernel: self._function_invoking_handlers = {} self._function_invoked_handlers = {} - @property - def logger(self) -> Logger: - return self._log - @property def memory(self) -> SemanticTextMemoryBase: return self._memory @@ -157,7 +154,7 @@ class Kernel: validate_skill_name(skill_name) validate_function_name(function_name) - function = SKFunction.from_native_method(sk_function, skill_name, self.logger) + function = SKFunction.from_native_method(sk_function, skill_name) if self.skills.has_function(skill_name, function_name): raise KernelException( @@ -222,7 +219,6 @@ class Kernel: variables, self._memory, self._skill_collection.read_only_skill_collection, - self._log, ) else: raise ValueError("No functions passed to run") @@ -237,7 +233,7 @@ class Kernel: except Exception as ex: # TODO: "critical exceptions" - self._log.error( + logger.error( "Something went wrong in stream function. During function invocation:" f" '{stream_function.skill_name}.{stream_function.name}'. Error" f" description: '{str(ex)}'" @@ -286,7 +282,6 @@ class Kernel: variables, self._memory, self._skill_collection.read_only_skill_collection, - self._log, ) pipeline_step = 0 @@ -298,7 +293,7 @@ class Kernel: ) if context.error_occurred: - self._log.error( + logger.error( f"Something went wrong in pipeline step {pipeline_step}. " f"Error description: '{context.last_error_description}'" ) @@ -315,7 +310,7 @@ class Kernel: and function_invoking_args.is_cancel_requested ): cancel_message = "Execution was cancelled on function invoking event of pipeline step" - self._log.info( + logger.info( f"{cancel_message} {pipeline_step}: {func.skill_name}.{func.name}." ) return context @@ -325,7 +320,7 @@ class Kernel: and function_invoking_args.is_skip_requested ): skip_message = "Execution was skipped on function invoking event of pipeline step" - self._log.info( + logger.info( f"{skip_message} {pipeline_step}: {func.skill_name}.{func.name}." ) break @@ -335,7 +330,7 @@ class Kernel: ) if context.error_occurred: - self._log.error( + logger.error( f"Something went wrong in pipeline step {pipeline_step}. " f"During function invocation: '{func.skill_name}.{func.name}'. " f"Error description: '{context.last_error_description}'" @@ -351,7 +346,7 @@ class Kernel: and function_invoked_args.is_cancel_requested ): cancel_message = "Execution was cancelled on function invoked event of pipeline step" - self._log.info( + logger.info( f"{cancel_message} {pipeline_step}: {func.skill_name}.{func.name}." ) return context @@ -360,7 +355,7 @@ class Kernel: and function_invoked_args.is_repeat_requested ): repeat_message = "Execution was repeated on function invoked event of pipeline step" - self._log.info( + logger.info( f"{repeat_message} {pipeline_step}: {func.skill_name}.{func.name}." ) continue @@ -368,7 +363,7 @@ class Kernel: break except Exception as ex: - self._log.error( + logger.error( f"Something went wrong in pipeline step {pipeline_step}. " f"During function invocation: '{func.skill_name}.{func.name}'. " f"Error description: '{str(ex)}'" @@ -422,7 +417,6 @@ class Kernel: ContextVariables() if not variables else variables, self._memory, self.skills, - self._log, ) def on_function_invoking( @@ -450,9 +444,9 @@ class Kernel: ) -> Dict[str, SKFunctionBase]: if skill_name.strip() == "": skill_name = SkillCollection.GLOBAL_SKILL - self._log.debug(f"Importing skill {skill_name} into the global namespace") + logger.debug(f"Importing skill {skill_name} into the global namespace") else: - self._log.debug(f"Importing skill {skill_name}") + logger.debug(f"Importing skill {skill_name}") functions = [] @@ -466,11 +460,9 @@ class Kernel: if not hasattr(candidate, "__sk_function__"): continue - functions.append( - SKFunction.from_native_method(candidate, skill_name, self.logger) - ) + functions.append(SKFunction.from_native_method(candidate, skill_name)) - self.logger.debug(f"Methods imported: {len(functions)}") + logger.debug(f"Methods imported: {len(functions)}") # Uniqueness check on function names function_names = [f.name for f in functions] diff --git a/python/semantic_kernel/memory/volatile_memory_store.py b/python/semantic_kernel/memory/volatile_memory_store.py index 5f8f90477e..3951ee57a1 100644 --- a/python/semantic_kernel/memory/volatile_memory_store.py +++ b/python/semantic_kernel/memory/volatile_memory_store.py @@ -1,28 +1,27 @@ # Copyright (c) Microsoft. All rights reserved. +import logging from copy import deepcopy -from logging import Logger -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Tuple from numpy import array, linalg, ndarray from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class VolatileMemoryStore(MemoryStoreBase): _store: Dict[str, Dict[str, MemoryRecord]] - _logger: Logger - def __init__(self, logger: Optional[Logger] = None) -> None: + def __init__(self, **kwargs) -> None: + """Initializes a new instance of the VolatileMemoryStore class.""" + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) self._store = {} - self._logger = logger or NullLogger() - """Initializes a new instance of the VolatileMemoryStore class. - - Arguments: - logger {Optional[Logger]} -- The logger to use. (default: {None}) - """ async def create_collection_async(self, collection_name: str) -> None: """Creates a new collection if it does not exist. @@ -313,7 +312,7 @@ class VolatileMemoryStore(MemoryStoreBase): embedding_array[valid_indices].T ) / (query_norm * collection_norm[valid_indices]) if not valid_indices.all(): - self._logger.warning( + logger.warning( "Some vectors in the embedding collection are zero vectors." "Ignoring cosine similarity score computation for those vectors." ) diff --git a/python/semantic_kernel/orchestration/sk_context.py b/python/semantic_kernel/orchestration/sk_context.py index 7ecad86427..46c302db41 100644 --- a/python/semantic_kernel/orchestration/sk_context.py +++ b/python/semantic_kernel/orchestration/sk_context.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import Any, Dict, Generic, Literal, Optional, Tuple, Union from pydantic import Field, PrivateAttr @@ -19,6 +19,8 @@ from semantic_kernel.skill_definition.read_only_skill_collection_base import ( ReadOnlySkillCollectionBase, ) +logger: logging.Logger = logging.getLogger(__name__) + class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]): """Semantic Kernel context.""" @@ -33,14 +35,13 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]): _error_occurred: bool = PrivateAttr(False) _last_exception: Optional[Exception] = PrivateAttr(None) _last_error_description: str = PrivateAttr("") - _logger: Logger = PrivateAttr() def __init__( self, variables: ContextVariables, memory: SemanticTextMemoryBase, skill_collection: Union[ReadOnlySkillCollection, None], - logger: Optional[Logger] = None, + **kwargs, # TODO: cancellation token? ) -> None: """ @@ -50,10 +51,11 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]): variables {ContextVariables} -- The context variables. memory {SemanticTextMemoryBase} -- The semantic text memory. skill_collection {ReadOnlySkillCollectionBase} -- The skill collection. - logger {Logger} -- The logger. """ - # Local import to avoid circular dependency - from semantic_kernel import NullLogger + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if skill_collection is None: skill_collection = ReadOnlySkillCollection() @@ -61,7 +63,6 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]): super().__init__( variables=variables, memory=memory, skill_collection=skill_collection ) - self._logger = logger or NullLogger() def fail(self, error_description: str, exception: Optional[Exception] = None): """ @@ -147,16 +148,6 @@ class SKContext(SKBaseModel, Generic[SemanticTextMemoryT]): """ self.skill_collection = value - @property - def log(self) -> Logger: - """ - The logger. - - Returns: - Logger -- The logger. - """ - return self._logger - def __setitem__(self, key: str, value: Any) -> None: """ Sets a context variable. diff --git a/python/semantic_kernel/orchestration/sk_function.py b/python/semantic_kernel/orchestration/sk_function.py index 2e3a317e0a..997ea28b62 100644 --- a/python/semantic_kernel/orchestration/sk_function.py +++ b/python/semantic_kernel/orchestration/sk_function.py @@ -1,11 +1,11 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio +import logging import platform import sys from concurrent.futures import ThreadPoolExecutor from enum import Enum -from logging import Logger from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional from semantic_kernel.connectors.ai.chat_completion_client_base import ( @@ -35,7 +35,6 @@ from semantic_kernel.skill_definition.parameter_view import ParameterView from semantic_kernel.skill_definition.read_only_skill_collection_base import ( ReadOnlySkillCollectionBase, ) -from semantic_kernel.utils.null_logger import NullLogger if TYPE_CHECKING: from semantic_kernel.orchestration.sk_context import SKContext @@ -43,6 +42,8 @@ if TYPE_CHECKING: if platform.system() == "Windows" and sys.version_info >= (3, 8, 0): asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) +logger: logging.Logger = logging.getLogger(__name__) + class SKFunction(SKFunctionBase): """ @@ -54,7 +55,6 @@ class SKFunction(SKFunctionBase): _delegate_type: DelegateTypes _function: Callable[..., Any] _skill_collection: Optional[ReadOnlySkillCollectionBase] - _log: Logger _ai_service: Optional[TextCompletionClientBase] _ai_request_settings: CompleteRequestSettings _chat_service: Optional[ChatCompletionClientBase] @@ -63,6 +63,10 @@ class SKFunction(SKFunctionBase): @staticmethod def from_native_method(method, skill_name="", log=None) -> "SKFunction": + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) if method is None: raise ValueError("Method cannot be `None`") @@ -110,7 +114,6 @@ class SKFunction(SKFunctionBase): skill_name=skill_name, function_name=method.__sk_function_name__, is_semantic=False, - log=log, ) @staticmethod @@ -118,8 +121,12 @@ class SKFunction(SKFunctionBase): skill_name: str, function_name: str, function_config: SemanticFunctionConfig, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> "SKFunction": + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) if function_config is None: raise ValueError("Function configuration cannot be `None`") @@ -149,7 +156,7 @@ class SKFunction(SKFunctionBase): else None ) if request_settings.function_call is not None and functions is None: - log.warning("Function call is not None, but functions is None") + logger.warning("Function call is not None, but functions is None") try: if functions and hasattr(client, "complete_chat_with_functions_async"): if ( @@ -277,7 +284,6 @@ class SKFunction(SKFunctionBase): skill_name=skill_name, function_name=function_name, is_semantic=True, - log=log, chat_prompt_template=function_config.prompt_template if function_config.has_chat_prompt else None, @@ -320,11 +326,15 @@ class SKFunction(SKFunctionBase): skill_name: str, function_name: str, is_semantic: bool, - log: Optional[Logger] = None, + log: Optional[Any] = None, delegate_stream_function: Optional[Callable[..., Any]] = None, **kwargs: Dict[str, Any], ) -> None: super().__init__() + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) self._delegate_type = delegate_type self._function = delegate_function self._parameters = parameters @@ -332,7 +342,6 @@ class SKFunction(SKFunctionBase): self._skill_name = skill_name self._name = function_name self._is_semantic = is_semantic - self._log = log if log is not None else NullLogger() self._stream_function = delegate_stream_function self._skill_collection = None self._ai_service = None @@ -395,15 +404,18 @@ class SKFunction(SKFunctionBase): context: Optional["SKContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[CompleteRequestSettings] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> "SKContext": + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) return self.invoke( input=input, variables=variables, context=context, memory=memory, settings=settings, - log=log, ) def invoke( @@ -413,16 +425,20 @@ class SKFunction(SKFunctionBase): context: Optional["SKContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[CompleteRequestSettings] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> "SKContext": from semantic_kernel.orchestration.sk_context import SKContext + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + if context is None: context = SKContext( variables=ContextVariables("") if variables is None else variables, skill_collection=self._skill_collection, memory=memory if memory is not None else NullMemory.instance, - logger=log if log is not None else self._log, ) else: # If context is passed, we need to merge the variables @@ -464,17 +480,21 @@ class SKFunction(SKFunctionBase): context: Optional["SKContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[CompleteRequestSettings] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, **kwargs: Dict[str, Any], ) -> "SKContext": from semantic_kernel.orchestration.sk_context import SKContext + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + if context is None: context = SKContext( variables=ContextVariables("") if variables is None else variables, skill_collection=self._skill_collection, memory=memory if memory is not None else NullMemory.instance, - logger=log if log is not None else self._log, ) else: # If context is passed, we need to merge the variables @@ -539,7 +559,7 @@ class SKFunction(SKFunctionBase): if self._is_semantic: return - self._log.error("The function is not semantic") + logger.error("The function is not semantic") raise KernelException( KernelException.ErrorCodes.InvalidFunctionType, "Invalid operation, the method requires a semantic function", @@ -549,7 +569,7 @@ class SKFunction(SKFunctionBase): if not self._is_semantic: return - self._log.error("The function is not native") + logger.error("The function is not native") raise KernelException( KernelException.ErrorCodes.InvalidFunctionType, "Invalid operation, the method requires a native function", @@ -562,7 +582,6 @@ class SKFunction(SKFunctionBase): context: Optional["SKContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[CompleteRequestSettings] = None, - log: Optional[Logger] = None, ): from semantic_kernel.orchestration.sk_context import SKContext @@ -571,7 +590,6 @@ class SKFunction(SKFunctionBase): variables=ContextVariables("") if variables is None else variables, skill_collection=self._skill_collection, memory=memory if memory is not None else NullMemory.instance, - logger=log if log is not None else self._log, ) else: # If context is passed, we need to merge the variables @@ -647,8 +665,8 @@ class SKFunction(SKFunctionBase): context.skills = self._skill_collection - def _trace_function_type_Call(self, type: Enum, log: Logger) -> None: - log.debug(f"Executing function type {type}: {type.name}") + def _trace_function_type_Call(self, type: Enum) -> None: + logger.debug(f"Executing function type {type}: {type.name}") """ Async code wrapper to allow running async code inside external diff --git a/python/semantic_kernel/orchestration/sk_function_base.py b/python/semantic_kernel/orchestration/sk_function_base.py index 18b2508e11..d9337b40d4 100644 --- a/python/semantic_kernel/orchestration/sk_function_base.py +++ b/python/semantic_kernel/orchestration/sk_function_base.py @@ -1,7 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. from abc import abstractmethod -from logging import Logger from typing import TYPE_CHECKING, Any, Callable, Dict, Optional from semantic_kernel.connectors.ai.complete_request_settings import ( @@ -103,7 +102,6 @@ class SKFunctionBase(SKBaseModel): context: Optional["SKContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[CompleteRequestSettings] = None, - log: Optional[Logger] = None, ) -> "SKContext": """ Invokes the function with an explicit string input @@ -113,7 +111,6 @@ class SKFunctionBase(SKBaseModel): context {SKContext} -- The context to use memory: {SemanticTextMemoryBase} -- The memory to use settings {CompleteRequestSettings} -- LLM completion settings - log {Logger} -- Application logger Returns: SKContext -- The updated context, potentially a new one if context switching is implemented. @@ -128,7 +125,6 @@ class SKFunctionBase(SKBaseModel): context: Optional["SKContext"] = None, memory: Optional[SemanticTextMemoryBase] = None, settings: Optional[CompleteRequestSettings] = None, - log: Optional[Logger] = None, **kwargs: Dict[str, Any], ) -> "SKContext": """ @@ -139,7 +135,6 @@ class SKFunctionBase(SKBaseModel): context {SKContext} -- The context to use memory: {SemanticTextMemoryBase} -- The memory to use settings {CompleteRequestSettings} -- LLM completion settings - log {Logger} -- Application logger Returns: SKContext -- The updated context, potentially a new one if context switching is implemented. diff --git a/python/semantic_kernel/planning/action_planner/action_planner.py b/python/semantic_kernel/planning/action_planner/action_planner.py index 078f2f8639..f1ce6f3104 100644 --- a/python/semantic_kernel/planning/action_planner/action_planner.py +++ b/python/semantic_kernel/planning/action_planner/action_planner.py @@ -2,8 +2,8 @@ import itertools import json +import logging import os -from logging import Logger from textwrap import dedent from typing import List, Optional @@ -20,7 +20,8 @@ from semantic_kernel.planning.planning_exception import PlanningException from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter from semantic_kernel.skill_definition.function_view import FunctionView from semantic_kernel.skill_definition.parameter_view import ParameterView -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class ActionPlanner: @@ -40,22 +41,24 @@ class ActionPlanner: _kernel: Kernel _prompt_template: str - _logger: Logger def __init__( self, kernel: Kernel, config: Optional[ActionPlannerConfig] = None, prompt: Optional[str] = None, - logger: Optional[Logger] = None, + **kwargs, ) -> None: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if kernel is None: raise PlanningException( PlanningException.ErrorCodes.InvalidConfiguration, "Kernel cannot be `None`.", ) - self._logger = logger if logger else NullLogger() self.config = config or ActionPlannerConfig() __cur_dir = os.path.dirname(os.path.abspath(__file__)) @@ -85,7 +88,7 @@ class ActionPlanner: PlanningException.ErrorCodes.InvalidGoal, "Goal cannot be `None`." ) - self._logger.info(f"Finding the best function for achieving the goal: {goal}") + logger.info(f"Finding the best function for achieving the goal: {goal}") self._context.variables.update(goal) @@ -95,20 +98,20 @@ class ActionPlanner: generated_plan_raw_str = str(generated_plan_raw) if not generated_plan_raw or not generated_plan_raw_str: - self._logger.error("No plan has been generated.") + logger.error("No plan has been generated.") raise PlanningException( PlanningException.ErrorCodes.CreatePlanError, "No plan has been generated.", ) - self._logger.info(f"Plan generated by ActionPlanner:\n{generated_plan_raw_str}") + logger.info(f"Plan generated by ActionPlanner:\n{generated_plan_raw_str}") # Ignore additional text around JSON recursively json_regex = r"\{(?:[^{}]|(?R))*\}" generated_plan_str = regex.search(json_regex, generated_plan_raw_str) if not generated_plan_str: - self._logger.error("No valid plan has been generated.") + logger.error("No valid plan has been generated.") raise PlanningException( PlanningException.ErrorCodes.InvalidPlan, "No valid plan has been generated.", @@ -121,19 +124,19 @@ class ActionPlanner: try: generated_plan = json.loads(generated_plan_str) except json.decoder.JSONDecodeError as e: - self._logger.error("Encountered an error while parsing Plan JSON.") - self._logger.error(e) + logger.error("Encountered an error while parsing Plan JSON.") + logger.error(e) raise PlanningException( PlanningException.ErrorCodes.InvalidPlan, "Encountered an error while parsing Plan JSON.", ) - self._logger.info( + logger.info( f"Python dictionary of plan generated by ActionPlanner:\n{generated_plan}" ) if not generated_plan["plan"]: - self._logger.error("Suitable plan not generated by ActionPlanner.") + logger.error("Suitable plan not generated by ActionPlanner.") raise PlanningException( PlanningException.ErrorCodes.CreatePlanError, "Suitable plan not generated by ActionPlanner.", @@ -141,14 +144,12 @@ class ActionPlanner: if not generated_plan["plan"]["function"]: # no suitable function identified, returning plan with no steps - self._logger.warn( - "No suitable function has been identified by ActionPlanner." - ) + logger.warn("No suitable function has been identified by ActionPlanner.") plan = Plan(description=goal) elif "." in generated_plan["plan"]["function"]: skill, fun = generated_plan["plan"]["function"].split(".") function_ref = self._context.skills.get_function(skill, fun) - self._logger.info( + logger.info( f"ActionPlanner has picked {skill}.{fun}. Reference to this function" f" found in context: {function_ref}" ) @@ -157,7 +158,7 @@ class ActionPlanner: function_ref = self._context.skills.get_function( generated_plan["plan"]["function"] ) - self._logger.info( + logger.info( f"ActionPlanner has picked {generated_plan['plan']['function']}. " " Reference to this function found in context:" f" {function_ref}" @@ -165,7 +166,7 @@ class ActionPlanner: plan = Plan(description=goal, function=function_ref) for key, val in generated_plan["plan"]["parameters"].items(): - self._logger.info(f"Parameter {key}: {val}") + logger.info(f"Parameter {key}: {val}") if val: plan.parameters[key] = str(val) plan.state[key] = str(val) @@ -279,7 +280,7 @@ class ActionPlanner: available_functions_str = "\n".join(available_functions) - self._logger.info(f"List of available functions:\n{available_functions_str}") + logger.info(f"List of available functions:\n{available_functions_str}") return available_functions_str @@ -297,7 +298,7 @@ class ActionPlanner: """ if not function.description: - self._logger.warn( + logger.warn( f"{function.skill_name}.{function.name} is missing a description" ) description = f"// Function {function.skill_name}.{function.name}." diff --git a/python/semantic_kernel/planning/plan.py b/python/semantic_kernel/planning/plan.py index 4f7f917d60..3485094f55 100644 --- a/python/semantic_kernel/planning/plan.py +++ b/python/semantic_kernel/planning/plan.py @@ -1,9 +1,9 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio +import logging import re import threading -from logging import Logger from typing import Any, Callable, ClassVar, List, Optional, Union from pydantic import PrivateAttr @@ -26,7 +26,8 @@ from semantic_kernel.skill_definition.read_only_skill_collection import ( from semantic_kernel.skill_definition.read_only_skill_collection_base import ( ReadOnlySkillCollectionBase, ) -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class Plan(SKFunctionBase): @@ -136,9 +137,13 @@ class Plan(SKFunctionBase): context: Optional[SKContext] = None, settings: Optional[CompleteRequestSettings] = None, memory: Optional[SemanticTextMemoryBase] = None, - logger: Optional[Logger] = None, + **kwargs, # TODO: cancellation_token: CancellationToken, ) -> SKContext: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if input is not None and input != "": self._state.update(input) @@ -147,7 +152,6 @@ class Plan(SKFunctionBase): variables=self._state, skill_collection=ReadOnlySkillCollection(), memory=memory or NullMemory(), - logger=logger if logger is not None else NullLogger(), ) if self._function is not None: @@ -155,7 +159,7 @@ class Plan(SKFunctionBase): context=context, settings=settings ) if result.error_occurred: - result.log.error( + logger.error( "Something went wrong in plan step {0}.{1}:'{2}'".format( self._skill_name, self._name, result.last_error_description ) @@ -178,8 +182,12 @@ class Plan(SKFunctionBase): context: Optional[SKContext] = None, settings: Optional[CompleteRequestSettings] = None, memory: Optional[SemanticTextMemoryBase] = None, - logger: Optional[Logger] = None, + **kwargs, ) -> SKContext: + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) if input is not None and input != "": self._state.update(input) @@ -188,13 +196,12 @@ class Plan(SKFunctionBase): variables=self._state, skill_collection=ReadOnlySkillCollection(), memory=memory or NullMemory(), - logger=logger, ) if self._function is not None: result = self._function.invoke(context=context, settings=settings) if result.error_occurred: - result.log.error( + logger.error( result.last_exception, "Something went wrong in plan step {0}.{1}:'{2}'".format( self.skill_name, self.name, context.last_error_description @@ -308,7 +315,6 @@ class Plan(SKFunctionBase): variables=variables, memory=context.memory, skill_collection=context.skills, - logger=context.log, ) result = await step.invoke_async(context=func_context) result_value = result.result diff --git a/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py b/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py index 8efa7c2e03..5e57eae98a 100644 --- a/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py +++ b/python/semantic_kernel/planning/sequential_planner/sequential_planner_extensions.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. import itertools +import logging from typing import AsyncIterable, List from semantic_kernel.kernel_exception import KernelException @@ -12,6 +13,8 @@ from semantic_kernel.planning.sequential_planner.sequential_planner_config impor ) from semantic_kernel.skill_definition.function_view import FunctionView +logger: logging.Logger = logging.getLogger(__name__) + class SequentialPlannerFunctionViewExtension: @staticmethod @@ -180,7 +183,7 @@ class SequentialPlannerSKContextExtension: None, ) if function is not None: - context.log.debug( + logger.debug( "Found relevant function. Relevance Score: {0}, Function: {1}".format( memory_entry.relevance, SequentialPlannerFunctionViewExtension.to_fully_qualified_name( diff --git a/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py b/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py index 660a956933..637aefee95 100644 --- a/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py +++ b/python/semantic_kernel/planning/stepwise_planner/stepwise_planner.py @@ -3,6 +3,7 @@ import asyncio import itertools import json +import logging import os import re from typing import TYPE_CHECKING, Dict, List @@ -31,6 +32,7 @@ from semantic_kernel.skill_definition.sk_function_decorator import sk_function if TYPE_CHECKING: from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +logger: logging.Logger = logging.getLogger(__name__) CUR_DIR = os.path.dirname(os.path.realpath(__file__)) PROMPT_CONFIG_FILE_PATH = os.path.join(CUR_DIR, "Skills/StepwiseStep/config.json") @@ -99,7 +101,6 @@ class StepwisePlanner: self._native_functions = self._kernel.import_skill(self, RESTRICTED_SKILL_NAME) self._context = kernel.create_new_context() - self._logger = self._kernel.logger def create_plan(self, goal: str) -> Plan: if is_null_or_empty(goal): @@ -154,13 +155,13 @@ class StepwisePlanner: ) action_text = llm_response.result.strip() - self._logger.debug(f"Response: {action_text}") + logger.debug(f"Response: {action_text}") next_step = self.parse_result(action_text) steps_taken.append(next_step) if not is_null_or_empty(next_step.final_answer): - self._logger.debug(f"Final Answer: {next_step.final_answer}") + logger.debug(f"Final Answer: {next_step.final_answer}") context.variables.update(next_step.final_answer) updated_scratch_pad = self.create_scratch_pad(question, steps_taken) @@ -171,11 +172,11 @@ class StepwisePlanner: return context - self._logger.debug("Thoughts: {next_step.thought}") + logger.debug("Thoughts: {next_step.thought}") if not is_null_or_empty(next_step.action): - self._logger.info(f"Action: {next_step.action}. Iteration: {i+1}.") - self._logger.debug( + logger.info(f"Action: {next_step.action}. Iteration: {i+1}.") + logger.debug( f"Action: {next_step.action}({next_step.action_variables}). Iteration: {i+1}.", ) @@ -194,13 +195,11 @@ class StepwisePlanner: next_step.observation = ( f"Error invoking action {next_step.action}: {str(e)}" ) - self._logger.warning( - f"Error invoking action {next_step.action}" - ) + logger.warning(f"Error invoking action {next_step.action}") - self._logger.debug(f"Observation: {next_step.observation}") + logger.debug(f"Observation: {next_step.observation}") else: - self._logger.info("Action: No action to take") + logger.info("Action: No action to take") # sleep 3 seconds await asyncio.sleep(self.config.min_iteration_time_ms / 1000) @@ -306,7 +305,7 @@ class StepwisePlanner: for i in reversed(range(len(steps_taken))): if len(scratch_pad_lines) / 4.0 > (self.config.max_tokens * 0.75): - self._logger.debug( + logger.debug( f"Scratchpad is too long, truncating. Skipping {i + 1} steps." ) break @@ -330,7 +329,7 @@ class StepwisePlanner: scratch_pad = "\n".join(scratch_pad_lines).strip() if not (is_null_or_empty(scratch_pad.strip())): - self._logger.debug(f"Scratchpad: {scratch_pad}") + logger.debug(f"Scratchpad: {scratch_pad}") return scratch_pad @@ -362,17 +361,15 @@ class StepwisePlanner: result = await function.invoke_async(context=action_context) if result.error_occurred: - self._logger.error(f"Error occurred: {result.last_exception}") + logger.error(f"Error occurred: {result.last_exception}") return f"Error occurred: {result.last_exception}" - self._logger.debug( - f"Invoked {target_function.name}. Result: {result.result}" - ) + logger.debug(f"Invoked {target_function.name}. Result: {result.result}") return result.result except Exception as e: - self._logger.error( + logger.error( e, f"Something went wrong in system step: {target_function.skill_name}.{target_function.name}. Error: {e}", ) diff --git a/python/semantic_kernel/reliability/pass_through_without_retry.py b/python/semantic_kernel/reliability/pass_through_without_retry.py index d6fce9003b..705aaaea0d 100644 --- a/python/semantic_kernel/reliability/pass_through_without_retry.py +++ b/python/semantic_kernel/reliability/pass_through_without_retry.py @@ -1,31 +1,36 @@ # Copyright (c) Microsoft. All rights reserved. import logging -from typing import Awaitable, Callable, TypeVar +from typing import Any, Awaitable, Callable, Optional, TypeVar from semantic_kernel.reliability.retry_mechanism_base import RetryMechanismBase from semantic_kernel.sk_pydantic import SKBaseModel T = TypeVar("T") +logger: logging.Logger = logging.getLogger(__name__) + class PassThroughWithoutRetry(RetryMechanismBase, SKBaseModel): """A retry mechanism that does not retry.""" async def execute_with_retry_async( - self, action: Callable[[], Awaitable[T]], log: logging.Logger + self, action: Callable[[], Awaitable[T]], log: Optional[Any] = None ) -> Awaitable[T]: """Executes the given action with retry logic. Arguments: action {Callable[[], Awaitable[T]]} -- The action to retry on exception. - log {logging.Logger} -- The logger to use. Returns: Awaitable[T] -- An awaitable that will return the result of the action. """ + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) try: await action() except Exception as e: - log.warning(e, "Error executing action, not retrying") + logger.warning(e, "Error executing action, not retrying") raise diff --git a/python/semantic_kernel/reliability/retry_mechanism_base.py b/python/semantic_kernel/reliability/retry_mechanism_base.py index 8e035d2813..fc531f72ca 100644 --- a/python/semantic_kernel/reliability/retry_mechanism_base.py +++ b/python/semantic_kernel/reliability/retry_mechanism_base.py @@ -2,23 +2,28 @@ import logging from abc import ABC, abstractmethod -from typing import Awaitable, Callable, TypeVar +from typing import Any, Awaitable, Callable, Optional, TypeVar T = TypeVar("T") +logger: logging.Logger = logging.getLogger(__name__) + class RetryMechanismBase(ABC): @abstractmethod async def execute_with_retry_async( - self, action: Callable[[], Awaitable[T]], log: logging.Logger + self, action: Callable[[], Awaitable[T]], log: Optional[Any] = None ) -> Awaitable[T]: """Executes the given action with retry logic. Arguments: action {Callable[[], Awaitable[T]]} -- The action to retry on exception. - log {logging.Logger} -- The logger to use. Returns: Awaitable[T] -- An awaitable that will return the result of the action. """ + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) pass diff --git a/python/semantic_kernel/semantic_functions/chat_prompt_template.py b/python/semantic_kernel/semantic_functions/chat_prompt_template.py index 8aa999e948..41de88093d 100644 --- a/python/semantic_kernel/semantic_functions/chat_prompt_template.py +++ b/python/semantic_kernel/semantic_functions/chat_prompt_template.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio -from logging import Logger +import logging from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, TypeVar from semantic_kernel.models.chat.chat_message import ChatMessage @@ -18,6 +18,8 @@ if TYPE_CHECKING: ChatMessageT = TypeVar("ChatMessageT", bound=ChatMessage) +logger: logging.Logger = logging.getLogger(__name__) + class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]): _messages: List[ChatMessageT] @@ -27,9 +29,13 @@ class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]): template: str, template_engine: PromptTemplatingEngine, prompt_config: PromptTemplateConfig, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: - super().__init__(template, template_engine, prompt_config, log) + super().__init__(template, template_engine, prompt_config) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) self._messages = [] if self._prompt_config.completion.chat_system_prompt: self.add_system_message(self._prompt_config.completion.chat_system_prompt) @@ -95,7 +101,7 @@ class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]): template: str, template_engine: PromptTemplatingEngine, prompt_config: PromptTemplateConfig, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> "ChatPromptTemplate": """Restore a ChatPromptTemplate from a list of role and message pairs. @@ -103,7 +109,11 @@ class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]): that takes precedence over the first message in the list of messages, if that is a system message. """ - chat_template = cls(template, template_engine, prompt_config, log) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + chat_template = cls(template, template_engine, prompt_config) if ( prompt_config.completion.chat_system_prompt and messages[0]["role"] == "system" @@ -113,7 +123,7 @@ class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]): existing_system_message["message"] != prompt_config.completion.chat_system_prompt ): - chat_template._log.info( + logger.info( "Overriding system prompt with chat_system_prompt, old system message: %s, new system message: %s", existing_system_message["message"], prompt_config.completion.chat_system_prompt, diff --git a/python/semantic_kernel/semantic_functions/prompt_template.py b/python/semantic_kernel/semantic_functions/prompt_template.py index b8ead27d11..d036184319 100644 --- a/python/semantic_kernel/semantic_functions/prompt_template.py +++ b/python/semantic_kernel/semantic_functions/prompt_template.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import TYPE_CHECKING, List, Optional +import logging +from typing import TYPE_CHECKING, Any, List, Optional from semantic_kernel.semantic_functions.prompt_template_base import PromptTemplateBase from semantic_kernel.semantic_functions.prompt_template_config import ( @@ -13,16 +13,16 @@ from semantic_kernel.template_engine.blocks.var_block import VarBlock from semantic_kernel.template_engine.protocols.prompt_templating_engine import ( PromptTemplatingEngine, ) -from semantic_kernel.utils.null_logger import NullLogger if TYPE_CHECKING: from semantic_kernel.orchestration.sk_context import SKContext +logger: logging.Logger = logging.getLogger(__name__) + class PromptTemplate(PromptTemplateBase): _template: str _template_engine: PromptTemplatingEngine - _log: Logger _prompt_config: PromptTemplateConfig def __init__( @@ -30,12 +30,15 @@ class PromptTemplate(PromptTemplateBase): template: str, template_engine: PromptTemplatingEngine, prompt_config: PromptTemplateConfig, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) self._template = template self._template_engine = template_engine self._prompt_config = prompt_config - self._log = log if log is not None else NullLogger() def get_parameters(self) -> List[ParameterView]: seen = set() diff --git a/python/semantic_kernel/skill_definition/read_only_skill_collection.py b/python/semantic_kernel/skill_definition/read_only_skill_collection.py index e8b361b7d1..fe33457818 100644 --- a/python/semantic_kernel/skill_definition/read_only_skill_collection.py +++ b/python/semantic_kernel/skill_definition/read_only_skill_collection.py @@ -1,9 +1,9 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Tuple +import logging +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple -from pydantic import ConfigDict, Field, PrivateAttr +from pydantic import ConfigDict, Field from semantic_kernel.kernel_exception import KernelException from semantic_kernel.orchestration.sk_function import SKFunction @@ -12,25 +12,29 @@ from semantic_kernel.skill_definition.functions_view import FunctionsView from semantic_kernel.skill_definition.read_only_skill_collection_base import ( ReadOnlySkillCollectionBase, ) -from semantic_kernel.utils.null_logger import NullLogger if TYPE_CHECKING: from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +logger: logging.Logger = logging.getLogger(__name__) + class ReadOnlySkillCollection(ReadOnlySkillCollectionBase): GLOBAL_SKILL: ClassVar[str] = constants.GLOBAL_SKILL data: Dict[str, Dict[str, SKFunction]] = Field(default_factory=dict) - _log: Logger = PrivateAttr(default_factory=NullLogger) model_config = ConfigDict(frozen=False) def __init__( self, data: Dict[str, Dict[str, SKFunction]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ) -> None: super().__init__(data=data or {}) - self._log = log or NullLogger() + + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) def has_function(self, skill_name: Optional[str], function_name: str) -> bool: s_name, f_name = self._normalize_names(skill_name, function_name, True) @@ -61,7 +65,7 @@ class ReadOnlySkillCollection(ReadOnlySkillCollectionBase): if self.has_semantic_function(s_name, f_name): return self.data[s_name][f_name] - self._log.error(f"Function not available: {s_name}.{f_name}") + logger.error(f"Function not available: {s_name}.{f_name}") raise KernelException( KernelException.ErrorCodes.FunctionNotAvailable, f"Function not available: {s_name}.{f_name}", @@ -74,7 +78,7 @@ class ReadOnlySkillCollection(ReadOnlySkillCollectionBase): if self.has_native_function(s_name, f_name): return self.data[s_name][f_name] - self._log.error(f"Function not available: {s_name}.{f_name}") + logger.error(f"Function not available: {s_name}.{f_name}") raise KernelException( KernelException.ErrorCodes.FunctionNotAvailable, f"Function not available: {s_name}.{f_name}", @@ -101,7 +105,7 @@ class ReadOnlySkillCollection(ReadOnlySkillCollectionBase): if self.has_function(s_name, f_name): return self.data[s_name][f_name] - self._log.error(f"Function not available: {s_name}.{f_name}") + logger.error(f"Function not available: {s_name}.{f_name}") raise KernelException( KernelException.ErrorCodes.FunctionNotAvailable, f"Function not available: {s_name}.{f_name}", diff --git a/python/semantic_kernel/skill_definition/skill_collection.py b/python/semantic_kernel/skill_definition/skill_collection.py index 64f4c1342f..6a99888ae3 100644 --- a/python/semantic_kernel/skill_definition/skill_collection.py +++ b/python/semantic_kernel/skill_definition/skill_collection.py @@ -1,15 +1,9 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import ( - TYPE_CHECKING, - ClassVar, - Dict, - Optional, - Union, -) +import logging +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union -from pydantic import Field, PrivateAttr +from pydantic import Field from semantic_kernel.orchestration.sk_function import SKFunction from semantic_kernel.skill_definition import constants @@ -21,25 +15,29 @@ from semantic_kernel.skill_definition.read_only_skill_collection_base import ( ReadOnlySkillCollectionBase, ) from semantic_kernel.skill_definition.skill_collection_base import SkillCollectionBase -from semantic_kernel.utils.null_logger import NullLogger if TYPE_CHECKING: from semantic_kernel.orchestration.sk_function_base import SKFunctionBase +logger: logging.Logger = logging.getLogger(__name__) + class SkillCollection(SkillCollectionBase): GLOBAL_SKILL: ClassVar[str] = constants.GLOBAL_SKILL read_only_skill_collection_: ReadOnlySkillCollection = Field( alias="read_only_skill_collection" ) - _log: Logger = PrivateAttr() def __init__( self, - log: Optional[Logger] = None, + log: Optional[Any] = None, skill_collection: Union[Dict[str, Dict[str, SKFunction]], None] = None, read_only_skill_collection_: Optional[ReadOnlySkillCollection] = None, ) -> None: + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) if skill_collection and read_only_skill_collection_: raise ValueError( "Only one of `skill_collection` and `read_only_skill_collection` can be" @@ -52,7 +50,6 @@ class SkillCollection(SkillCollectionBase): else: read_only_skill_collection = read_only_skill_collection_ super().__init__(read_only_skill_collection=read_only_skill_collection) - self._log = log or NullLogger() @property def read_only_skill_collection(self) -> ReadOnlySkillCollectionBase: diff --git a/python/semantic_kernel/template_engine/blocks/block.py b/python/semantic_kernel/template_engine/blocks/block.py index 5238f74730..c74f765e89 100644 --- a/python/semantic_kernel/template_engine/blocks/block.py +++ b/python/semantic_kernel/template_engine/blocks/block.py @@ -1,24 +1,26 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import Optional, Tuple - -from pydantic import PrivateAttr +import logging +from typing import Any, Optional, Tuple from semantic_kernel.sk_pydantic import SKBaseModel from semantic_kernel.template_engine.blocks.block_types import BlockTypes -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) class Block(SKBaseModel): content: Optional[str] = None - _log: Optional[Logger] = PrivateAttr(default_factory=NullLogger) def __init__( - self, content: Optional[str] = None, log: Optional[Logger] = None + self, content: Optional[str] = None, log: Optional[Any] = None ) -> None: super().__init__(content=content) - self._log = log or NullLogger() + + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) def is_valid(self) -> Tuple[bool, str]: raise NotImplementedError("Subclasses must implement this method.") @@ -26,7 +28,3 @@ class Block(SKBaseModel): @property def type(self) -> BlockTypes: return BlockTypes.UNDEFINED - - @property - def log(self) -> Logger: - return self._log diff --git a/python/semantic_kernel/template_engine/blocks/code_block.py b/python/semantic_kernel/template_engine/blocks/code_block.py index 59580bdd35..4c49e9319a 100644 --- a/python/semantic_kernel/template_engine/blocks/code_block.py +++ b/python/semantic_kernel/template_engine/blocks/code_block.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import List, Optional, Tuple +import logging +from typing import Any, List, Optional, Tuple import pydantic as pdt @@ -14,6 +14,8 @@ from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdBlock from semantic_kernel.template_engine.code_tokenizer import CodeTokenizer +logger: logging.Logger = logging.getLogger(__name__) + class CodeBlock(Block): _tokens: List[Block] = pdt.PrivateAttr() @@ -23,11 +25,16 @@ class CodeBlock(Block): self, content: str, tokens: Optional[List[Block]] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ): - super().__init__(content=content and content.strip(), log=log) + super().__init__(content=content and content.strip()) - self._tokens = tokens or CodeTokenizer(log=self.log).tokenize(content) + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + + self._tokens = tokens or CodeTokenizer().tokenize(content) self._validated = False @property @@ -40,13 +47,13 @@ class CodeBlock(Block): for token in self._tokens: is_valid, error_msg = token.is_valid() if not is_valid: - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if len(self._tokens) > 1: if self._tokens[0].type != BlockTypes.FUNCTION_ID: error_msg = f"Unexpected second token found: {self._tokens[1].content}" - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if ( @@ -54,12 +61,12 @@ class CodeBlock(Block): and self._tokens[1].type != BlockTypes.VARIABLE ): error_msg = "Functions support only one parameter" - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if len(self._tokens) > 2: error_msg = f"Unexpected second token found: {self._tokens[1].content}" - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg self._validated = True @@ -72,7 +79,7 @@ class CodeBlock(Block): if not is_valid: raise ValueError(error) - self.log.debug(f"Rendering code: `{self.content}`") + logger.debug(f"Rendering code: `{self.content}`") if self._tokens[0].type in (BlockTypes.VALUE, BlockTypes.VARIABLE): return self._tokens[0].render(context.variables) @@ -90,18 +97,18 @@ class CodeBlock(Block): if not function: error_msg = f"Function `{f_block.content}` not found" - self.log.error(error_msg) + logger.error(error_msg) raise ValueError(error_msg) variables_clone = context.variables.clone() if len(self._tokens) > 1: - self.log.debug(f"Passing variable/value: `{self._tokens[1].content}`") + logger.debug(f"Passing variable/value: `{self._tokens[1].content}`") input_value = self._tokens[1].render(variables_clone) variables_clone.update(input_value) result = await function.invoke_async( - variables=variables_clone, memory=context.memory, log=self.log + variables=variables_clone, memory=context.memory ) if result.error_occurred: @@ -110,7 +117,7 @@ class CodeBlock(Block): f"{result.last_exception.__class__.__name__}: " f"{result.last_error_description}" ) - self.log.error(error_msg) + logger.error(error_msg) raise ValueError(error_msg) return result.result 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 83e3f952b6..398de21608 100644 --- a/python/semantic_kernel/template_engine/blocks/function_id_block.py +++ b/python/semantic_kernel/template_engine/blocks/function_id_block.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from re import match as re_match -from typing import Optional, Tuple +from typing import Any, Optional, Tuple import pydantic as pdt @@ -10,17 +10,24 @@ from semantic_kernel.orchestration.context_variables import ContextVariables from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes +logger: logging.Logger = logging.getLogger(__name__) + class FunctionIdBlock(Block): _skill_name: str = pdt.PrivateAttr() _function_name: str = pdt.PrivateAttr() - def __init__(self, content: Optional[str] = None, log: Optional[Logger] = None): - super().__init__(content=content and content.strip(), log=log) + def __init__(self, content: Optional[str] = None, log: Optional[Any] = None): + super().__init__(content=content and content.strip()) + + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) function_name_parts = self.content.split(".") if len(function_name_parts) > 2: - self.log.error(f"Invalid function name `{self.content}`") + logger.error(f"Invalid function name `{self.content}`") raise ValueError( "A function name can contain at most one dot separating " "the skill name from the function name" diff --git a/python/semantic_kernel/template_engine/blocks/text_block.py b/python/semantic_kernel/template_engine/blocks/text_block.py index d94530b89f..844071bea7 100644 --- a/python/semantic_kernel/template_engine/blocks/text_block.py +++ b/python/semantic_kernel/template_engine/blocks/text_block.py @@ -1,12 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import Optional, Tuple +import logging +from typing import Any, Optional, Tuple from semantic_kernel.orchestration.context_variables import ContextVariables from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes +logger: logging.Logger = logging.getLogger(__name__) + class TextBlock(Block): @classmethod @@ -15,10 +17,14 @@ class TextBlock(Block): text: Optional[str] = None, start_index: Optional[int] = None, stop_index: Optional[int] = None, - log: Optional[Logger] = None, + log: Optional[Any] = None, ): + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) if text is None: - return cls(content="", log=log) + return cls(content="") if start_index is not None and stop_index is not None: if start_index > stop_index: raise ValueError( @@ -35,7 +41,7 @@ class TextBlock(Block): elif stop_index is not None: text = text[:stop_index] - return cls(content=text, log=log) + return cls(content=text) @property def type(self) -> BlockTypes: diff --git a/python/semantic_kernel/template_engine/blocks/val_block.py b/python/semantic_kernel/template_engine/blocks/val_block.py index 07a8a881f6..841580c63d 100644 --- a/python/semantic_kernel/template_engine/blocks/val_block.py +++ b/python/semantic_kernel/template_engine/blocks/val_block.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import Optional, Tuple +import logging +from typing import Any, Optional, Tuple import pydantic as pdt @@ -10,18 +10,25 @@ from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.blocks.symbols import Symbols +logger: logging.Logger = logging.getLogger(__name__) + class ValBlock(Block): _first: str = pdt.PrivateAttr() _last: str = pdt.PrivateAttr() _value: str = pdt.PrivateAttr() - def __init__(self, content: Optional[str] = None, log: Optional[Logger] = None): - super().__init__(content=content and content.strip(), log=log) + def __init__(self, content: Optional[str] = None, log: Optional[Any] = None): + super().__init__(content=content and content.strip()) + + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) if len(self.content) < 2: err = "A value must have single quotes or double quotes on both sides" - self.log.error(err) + logger.error(err) self._value = "" self._first = "\0" self._last = "\0" @@ -38,14 +45,14 @@ class ValBlock(Block): def is_valid(self) -> Tuple[bool, str]: if len(self.content) < 2: error_msg = "A value must have single quotes or double quotes on both sides" - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if self._first != Symbols.DBL_QUOTE and self._first != Symbols.SGL_QUOTE: error_msg = ( "A value must be wrapped in either single quotes or double quotes" ) - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if self._first != self._last: @@ -53,7 +60,7 @@ class ValBlock(Block): "A value must be defined using either single quotes or " "double quotes, not both" ) - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg return True, "" diff --git a/python/semantic_kernel/template_engine/blocks/var_block.py b/python/semantic_kernel/template_engine/blocks/var_block.py index 0e77166ead..6a21a82b2b 100644 --- a/python/semantic_kernel/template_engine/blocks/var_block.py +++ b/python/semantic_kernel/template_engine/blocks/var_block.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from re import match as re_match -from typing import Optional, Tuple +from typing import Any, Optional, Tuple import pydantic as pdt @@ -11,16 +11,23 @@ from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.blocks.symbols import Symbols +logger: logging.Logger = logging.getLogger(__name__) + class VarBlock(Block): _name: str = pdt.PrivateAttr() - def __init__(self, content: Optional[str] = None, log: Optional[Logger] = None): - super().__init__(content=content and content.strip(), log=log) + def __init__(self, content: Optional[str] = None, log: Optional[Any] = None): + super().__init__(content=content and content.strip()) + + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) if len(self.content) < 2: err = "The variable name is empty" - self.log.error(err) + logger.error(err) self._name = "" return @@ -44,17 +51,17 @@ class VarBlock(Block): f"A variable must start with the symbol {Symbols.VAR_PREFIX} " "and have a name" ) - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if self.content[0] != Symbols.VAR_PREFIX: error_msg = f"A variable must start with the symbol {Symbols.VAR_PREFIX}" - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if len(self.content) < 2: error_msg = "The variable name is empty" - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg if not re_match(r"^[a-zA-Z0-9_]*$", self.name): @@ -62,7 +69,7 @@ class VarBlock(Block): f"The variable name '{self.name}' contains invalid characters. " "Only alphanumeric chars and underscore are allowed." ) - self.log.error(error_msg) + logger.error(error_msg) return False, error_msg return True, "" @@ -73,11 +80,11 @@ class VarBlock(Block): if not self.name: error_msg = "Variable rendering failed, the variable name is empty" - self.log.error(error_msg) + logger.error(error_msg) raise ValueError(error_msg) value = variables.get(self.name, None) if not value: - self.log.warning(f"Variable `{Symbols.VAR_PREFIX}{self.name}` not found") + logger.warning(f"Variable `{Symbols.VAR_PREFIX}{self.name}` not found") return value or "" diff --git a/python/semantic_kernel/template_engine/code_tokenizer.py b/python/semantic_kernel/template_engine/code_tokenizer.py index 3e0f2e60bf..c58e5908ce 100644 --- a/python/semantic_kernel/template_engine/code_tokenizer.py +++ b/python/semantic_kernel/template_engine/code_tokenizer.py @@ -1,9 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import List, Optional - -from pydantic import PrivateAttr +import logging +from typing import Any, List, Optional from semantic_kernel.sk_pydantic import SKBaseModel from semantic_kernel.template_engine.blocks.block import Block @@ -12,7 +10,8 @@ from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdB from semantic_kernel.template_engine.blocks.symbols import Symbols from semantic_kernel.template_engine.blocks.val_block import ValBlock from semantic_kernel.template_engine.blocks.var_block import VarBlock -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) # BNF parsed by CodeTokenizer: @@ -24,15 +23,13 @@ from semantic_kernel.utils.null_logger import NullLogger # [function-call] ::= [function-id] | [function-id] [parameter] # [parameter] ::= [variable] | [value] class CodeTokenizer(SKBaseModel): - _log: Optional[Logger] = PrivateAttr(default_factory=NullLogger) - - def __init__(self, log: Logger = None): + def __init__(self, log: Optional[Any] = None): super().__init__() - self._log = log or NullLogger() - @property - def log(self) -> Logger: - return self._log + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) def tokenize(self, text: str) -> List[Block]: # Remove spaces, which are ignored anyway @@ -58,11 +55,11 @@ class CodeTokenizer(SKBaseModel): # 1 char only edge case if len(text) == 1: if next_char == Symbols.VAR_PREFIX: - blocks.append(VarBlock(text, self.log)) + blocks.append(VarBlock(text)) elif next_char in (Symbols.DBL_QUOTE, Symbols.SGL_QUOTE): - blocks.append(ValBlock(text, self.log)) + blocks.append(ValBlock(text)) else: - blocks.append(FunctionIdBlock(text, self.log)) + blocks.append(FunctionIdBlock(text)) return blocks @@ -104,7 +101,7 @@ class CodeTokenizer(SKBaseModel): # When we reach the end of the value, we add the block if current_char == text_value_delimiter: - blocks.append(ValBlock("".join(current_token_content), self.log)) + blocks.append(ValBlock("".join(current_token_content))) current_token_content.clear() current_token_type = None space_separator_found = False @@ -115,12 +112,10 @@ class CodeTokenizer(SKBaseModel): # Note: there might be multiple consecutive spaces if self._is_blank_space(current_char): if current_token_type == BlockTypes.VARIABLE: - blocks.append(VarBlock("".join(current_token_content), self.log)) + blocks.append(VarBlock("".join(current_token_content))) current_token_content.clear() elif current_token_type == BlockTypes.FUNCTION_ID: - blocks.append( - FunctionIdBlock("".join(current_token_content), self.log) - ) + blocks.append(FunctionIdBlock("".join(current_token_content))) current_token_content.clear() space_separator_found = True @@ -150,11 +145,11 @@ class CodeTokenizer(SKBaseModel): current_token_content.append(next_char) if current_token_type == BlockTypes.VALUE: - blocks.append(ValBlock("".join(current_token_content), self.log)) + blocks.append(ValBlock("".join(current_token_content))) elif current_token_type == BlockTypes.VARIABLE: - blocks.append(VarBlock("".join(current_token_content), self.log)) + blocks.append(VarBlock("".join(current_token_content))) elif current_token_type == BlockTypes.FUNCTION_ID: - blocks.append(FunctionIdBlock("".join(current_token_content), self.log)) + blocks.append(FunctionIdBlock("".join(current_token_content))) else: raise ValueError("Tokens must be separated by one space least") diff --git a/python/semantic_kernel/template_engine/prompt_template_engine.py b/python/semantic_kernel/template_engine/prompt_template_engine.py index 905f621ced..83cc8d01e7 100644 --- a/python/semantic_kernel/template_engine/prompt_template_engine.py +++ b/python/semantic_kernel/template_engine/prompt_template_engine.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger +import logging from typing import TYPE_CHECKING, List, Optional from pydantic import PrivateAttr @@ -11,24 +11,24 @@ from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.protocols.text_renderer import TextRenderer from semantic_kernel.template_engine.template_tokenizer import TemplateTokenizer -from semantic_kernel.utils.null_logger import NullLogger if TYPE_CHECKING: from semantic_kernel.orchestration.sk_context import SKContext +logger: logging.Logger = logging.getLogger(__name__) + class PromptTemplateEngine(SKBaseModel): - _log: Optional[Logger] = PrivateAttr(default_factory=NullLogger) _tokenizer: TemplateTokenizer = PrivateAttr() - def __init__(self, logger: Optional[Logger] = None) -> None: + def __init__(self, **kwargs) -> None: super().__init__() - self._log = logger or NullLogger() - self._tokenizer = TemplateTokenizer(log=self.log) - @property - def log(self) -> Logger: - return self._log + if kwargs.get("logger"): + logger.warning( + "The `logger` parameter is deprecated. Please use the `logging` module instead." + ) + self._tokenizer = TemplateTokenizer() def extract_blocks( self, template_text: Optional[str] = None, validate: bool = True @@ -43,7 +43,7 @@ class PromptTemplateEngine(SKBaseModel): :return: A list of all the blocks, ie the template tokenized in text, variables and function calls """ - self.log.debug(f"Extracting blocks from template: {template_text}") + logger.debug(f"Extracting blocks from template: {template_text}") blocks = self._tokenizer.tokenize(template_text) if validate: @@ -64,7 +64,7 @@ class PromptTemplateEngine(SKBaseModel): :param context: Access into the current kernel execution context :return: The prompt template ready to be used for an AI request """ - self.log.debug(f"Rendering string template: {template_text}") + logger.debug(f"Rendering string template: {template_text}") blocks = self.extract_blocks(template_text) return await self.render_blocks_async(blocks, context) @@ -80,7 +80,7 @@ class PromptTemplateEngine(SKBaseModel): """ from semantic_kernel.template_engine.protocols.code_renderer import CodeRenderer - self.log.debug(f"Rendering list of {len(blocks)} blocks") + logger.debug(f"Rendering list of {len(blocks)} blocks") rendered_blocks = [] for block in blocks: if isinstance(block, TextRenderer): @@ -92,10 +92,10 @@ class PromptTemplateEngine(SKBaseModel): "unexpected block type, the block doesn't have a rendering " "protocol assigned to it" ) - self.log.error(error) + logger.error(error) raise ValueError(error) - self.log.debug(f"Rendered prompt: {''.join(rendered_blocks)}") + logger.debug(f"Rendered prompt: {''.join(rendered_blocks)}") return "".join(rendered_blocks) def render_variables( @@ -112,7 +112,7 @@ class PromptTemplateEngine(SKBaseModel): """ from semantic_kernel.template_engine.blocks.text_block import TextBlock - self.log.debug("Rendering variables") + logger.debug("Rendering variables") rendered_blocks = [] for block in blocks: @@ -121,9 +121,7 @@ class PromptTemplateEngine(SKBaseModel): continue if not isinstance(block, TextRenderer): raise ValueError("TextBlock must implement TextRenderer protocol") - rendered_blocks.append( - TextBlock.from_text(block.render(variables), log=self.log) - ) + rendered_blocks.append(TextBlock.from_text(block.render(variables))) return rendered_blocks @@ -142,7 +140,7 @@ class PromptTemplateEngine(SKBaseModel): from semantic_kernel.template_engine.blocks.text_block import TextBlock from semantic_kernel.template_engine.protocols.code_renderer import CodeRenderer - self.log.debug("Rendering code") + logger.debug("Rendering code") rendered_blocks = [] for block in blocks: @@ -152,9 +150,7 @@ class PromptTemplateEngine(SKBaseModel): if not isinstance(block, CodeRenderer): raise ValueError("CodeBlock must implement CodeRenderer protocol") rendered_blocks.append( - TextBlock.from_text( - await block.render_code_async(execution_context), log=self.log - ) + TextBlock.from_text(await block.render_code_async(execution_context)) ) return rendered_blocks diff --git a/python/semantic_kernel/template_engine/template_tokenizer.py b/python/semantic_kernel/template_engine/template_tokenizer.py index be8bdee9d1..8b209a031f 100644 --- a/python/semantic_kernel/template_engine/template_tokenizer.py +++ b/python/semantic_kernel/template_engine/template_tokenizer.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger -from typing import List, Optional +import logging +from typing import Any, List, Optional from pydantic import PrivateAttr @@ -12,7 +12,8 @@ from semantic_kernel.template_engine.blocks.code_block import CodeBlock from semantic_kernel.template_engine.blocks.symbols import Symbols from semantic_kernel.template_engine.blocks.text_block import TextBlock from semantic_kernel.template_engine.code_tokenizer import CodeTokenizer -from semantic_kernel.utils.null_logger import NullLogger + +logger: logging.Logger = logging.getLogger(__name__) # BNF parsed by TemplateTokenizer: @@ -24,17 +25,15 @@ from semantic_kernel.utils.null_logger import NullLogger # [text-block] ::= [any-char] | [any-char] [text-block] # [any-char] ::= any char class TemplateTokenizer(SKBaseModel): - _log: Optional[Logger] = PrivateAttr(default_factory=NullLogger) _code_tokenizer: CodeTokenizer = PrivateAttr() - def __init__(self, log: Logger = None): + def __init__(self, log: Optional[Any] = None): super().__init__() - self._log = log or NullLogger() - self._code_tokenizer = CodeTokenizer(log=self.log) - - @property - def log(self) -> Logger: - return self._log + if log: + logger.warning( + "The `log` parameter is deprecated. Please use the `logging` module instead." + ) + self._code_tokenizer = CodeTokenizer() def tokenize(self, text: str) -> List[Block]: # An empty block consists of 4 chars: "{{}}" @@ -47,11 +46,11 @@ class TemplateTokenizer(SKBaseModel): # Render None/empty to "" if not text or text == "": - return [TextBlock.from_text("", log=self.log)] + return [TextBlock.from_text("")] # If the template is "empty" return it as a text block if len(text) < MIN_CODE_BLOCK_LENGTH: - return [TextBlock.from_text(text, log=self.log)] + return [TextBlock.from_text(text)] blocks = [] end_of_last_block = 0 @@ -114,7 +113,6 @@ class TemplateTokenizer(SKBaseModel): text, end_of_last_block, block_start_pos, - log=self.log, ) ) @@ -130,11 +128,7 @@ class TemplateTokenizer(SKBaseModel): if len(content_without_delimiters) == 0: # If what is left is empty, consider the raw block # a TextBlock - blocks.append( - TextBlock.from_text( - content_with_delimiters, log=self.log - ) - ) + blocks.append(TextBlock.from_text(content_with_delimiters)) else: code_blocks = self._code_tokenizer.tokenize( content_without_delimiters @@ -169,7 +163,6 @@ class TemplateTokenizer(SKBaseModel): CodeBlock( content_without_delimiters, code_blocks, - self.log, ) ) else: @@ -183,9 +176,7 @@ class TemplateTokenizer(SKBaseModel): # If there is something left after the last block, capture it as a TextBlock if end_of_last_block < len(text): - blocks.append( - TextBlock.from_text(text, end_of_last_block, len(text), log=self.log) - ) + blocks.append(TextBlock.from_text(text, end_of_last_block, len(text))) return blocks diff --git a/python/semantic_kernel/utils/logging.py b/python/semantic_kernel/utils/logging.py new file mode 100644 index 0000000000..3a171572a2 --- /dev/null +++ b/python/semantic_kernel/utils/logging.py @@ -0,0 +1,11 @@ +# Copyright (c) Microsoft. All rights reserved. + +import logging + + +def setup_logging(): + # Setup a detailed logging format. + logging.basicConfig( + format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) diff --git a/python/semantic_kernel/utils/null_logger.py b/python/semantic_kernel/utils/null_logger.py index 586d3d1d27..d607240fd4 100644 --- a/python/semantic_kernel/utils/null_logger.py +++ b/python/semantic_kernel/utils/null_logger.py @@ -1,9 +1,11 @@ # Copyright (c) Microsoft. All rights reserved. from functools import wraps -from logging import Logger +from logging import Logger, getLogger from typing import Any, Callable +logger: Logger = getLogger(__name__) + def _nullify(fn) -> Callable[[Any], None]: """General wrapper to not call wrapped function""" @@ -35,6 +37,12 @@ class NullLogger(Logger, metaclass=_NullerMeta): def __init__(self): super().__init__(None) + logger.warning( + ( + "NullLogger is deprecated and will be removed in a future release,", + "the same goes for all 'log' and 'logger' arguments.", + ) + ) __all__ = ["NullLogger"] diff --git a/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py b/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py index e0771c33b8..5cdbe0b7e5 100644 --- a/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py +++ b/python/tests/unit/ai/open_ai/services/test_azure_chat_completion.py @@ -30,7 +30,6 @@ def test_azure_chat_completion_init() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") # Test successful initialization azure_chat_completion = AzureChatCompletion( @@ -38,7 +37,6 @@ def test_azure_chat_completion_init() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) assert azure_chat_completion.client is not None @@ -52,7 +50,6 @@ def test_azure_chat_completion_init_base_url() -> None: base_url = "https://test-endpoint.com/openai/deployment/test_deployment" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") # Custom header for testing default_headers = {"X-Unit-Test": "test-guid"} @@ -62,7 +59,6 @@ def test_azure_chat_completion_init_base_url() -> None: base_url=base_url, api_key=api_key, api_version=api_version, - log=logger, default_headers=default_headers, ) @@ -80,7 +76,6 @@ def test_azure_chat_completion_init_with_empty_deployment_name() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="ai_model_id"): AzureChatCompletion( @@ -88,7 +83,6 @@ def test_azure_chat_completion_init_with_empty_deployment_name() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) @@ -97,7 +91,6 @@ def test_azure_chat_completion_init_with_empty_api_key() -> None: endpoint = "https://test-endpoint.com" # api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(AIException, match="api_key"): AzureChatCompletion( @@ -105,7 +98,6 @@ def test_azure_chat_completion_init_with_empty_api_key() -> None: endpoint=endpoint, api_key="", api_version=api_version, - log=logger, ) @@ -114,7 +106,6 @@ def test_azure_chat_completion_init_with_empty_endpoint() -> None: # endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="url"): AzureChatCompletion( @@ -122,7 +113,6 @@ def test_azure_chat_completion_init_with_empty_endpoint() -> None: endpoint="", api_key=api_key, api_version=api_version, - log=logger, ) @@ -131,7 +121,6 @@ def test_azure_chat_completion_init_with_invalid_endpoint() -> None: endpoint = "http://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="url"): AzureChatCompletion( @@ -139,7 +128,6 @@ def test_azure_chat_completion_init_with_invalid_endpoint() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) @@ -148,7 +136,6 @@ def test_azure_chat_completion_init_with_base_url() -> None: base_url = "http://test-endpoint.com/openai/deployment/test_deployment" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="url"): AzureChatCompletion( @@ -156,7 +143,6 @@ def test_azure_chat_completion_init_with_base_url() -> None: base_url=base_url, api_key=api_key, api_version=api_version, - log=logger, ) @@ -167,7 +153,7 @@ async def test_azure_chat_completion_call_with_parameters(mock_create) -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") + prompt = "hello world" messages_in = [{"role": "user", "content": prompt}] messages_out = [{"role": "user", "content": prompt}] @@ -178,7 +164,6 @@ async def test_azure_chat_completion_call_with_parameters(mock_create) -> None: endpoint=endpoint, api_version=api_version, api_key=api_key, - log=logger, ) await azure_chat_completion.complete_chat_async( messages=messages_in, settings=complete_request_settings @@ -207,7 +192,7 @@ async def test_azure_chat_completion_call_with_parameters_and_Logit_Bias_Defined endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") + prompt = "hello world" messages = [{"role": "user", "content": prompt}] complete_request_settings = ChatRequestSettings() @@ -220,7 +205,6 @@ async def test_azure_chat_completion_call_with_parameters_and_Logit_Bias_Defined endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) await azure_chat_completion.complete_chat_async( @@ -251,7 +235,7 @@ async def test_azure_chat_completion_call_with_parameters_and_Stop_Defined( endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") + prompt = "hello world" messages = [{"role": "user", "content": prompt}] complete_request_settings = ChatRequestSettings() @@ -264,7 +248,6 @@ async def test_azure_chat_completion_call_with_parameters_and_Stop_Defined( endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) await azure_chat_completion.complete_async(prompt, complete_request_settings) @@ -290,14 +273,12 @@ def test_azure_chat_completion_serialize() -> None: api_key = "test_api_key" api_version = "2023-03-15-preview" default_headers = {"X-Test": "test"} - logger = Logger("test_logger") settings = { "deployment_name": deployment_name, "endpoint": endpoint, "api_key": api_key, "api_version": api_version, - "log": logger, "default_headers": default_headers, } diff --git a/python/tests/unit/ai/open_ai/services/test_azure_text_completion.py b/python/tests/unit/ai/open_ai/services/test_azure_text_completion.py index b6517072df..6affbc0859 100644 --- a/python/tests/unit/ai/open_ai/services/test_azure_text_completion.py +++ b/python/tests/unit/ai/open_ai/services/test_azure_text_completion.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger from unittest.mock import AsyncMock, patch import pytest @@ -23,7 +22,6 @@ def test_azure_text_completion_init() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") # Test successful initialization azure_text_completion = AzureTextCompletion( @@ -31,7 +29,6 @@ def test_azure_text_completion_init() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) assert azure_text_completion.client is not None @@ -45,7 +42,6 @@ def test_azure_text_completion_init_with_custom_header() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") # Custom header for testing default_headers = {"X-Unit-Test": "test-guid"} @@ -56,7 +52,6 @@ def test_azure_text_completion_init_with_custom_header() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, default_headers=default_headers, ) @@ -74,7 +69,6 @@ def test_azure_text_completion_init_with_empty_deployment_name() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="ai_model_id"): AzureTextCompletion( @@ -82,7 +76,6 @@ def test_azure_text_completion_init_with_empty_deployment_name() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) @@ -91,7 +84,6 @@ def test_azure_text_completion_init_with_empty_api_key() -> None: endpoint = "https://test-endpoint.com" # api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(AIException, match="api_key"): AzureTextCompletion( @@ -99,7 +91,6 @@ def test_azure_text_completion_init_with_empty_api_key() -> None: endpoint=endpoint, api_key="", api_version=api_version, - log=logger, ) @@ -108,7 +99,6 @@ def test_azure_text_completion_init_with_empty_endpoint() -> None: # endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="endpoint"): AzureTextCompletion( @@ -116,7 +106,6 @@ def test_azure_text_completion_init_with_empty_endpoint() -> None: endpoint="", api_key=api_key, api_version=api_version, - log=logger, ) @@ -125,7 +114,6 @@ def test_azure_text_completion_init_with_invalid_endpoint() -> None: endpoint = "http://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="https"): AzureTextCompletion( @@ -133,7 +121,6 @@ def test_azure_text_completion_init_with_invalid_endpoint() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) @@ -144,7 +131,7 @@ async def test_azure_text_completion_call_with_parameters(mock_create) -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") + prompt = "hello world" complete_request_settings = CompleteRequestSettings() azure_text_completion = AzureTextCompletion( @@ -152,7 +139,6 @@ async def test_azure_text_completion_call_with_parameters(mock_create) -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) await azure_text_completion.complete_async(prompt, complete_request_settings) @@ -182,7 +168,7 @@ async def test_azure_text_completion_call_with_parameters_logit_bias_not_none( endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") + prompt = "hello world" complete_request_settings = CompleteRequestSettings() @@ -194,7 +180,6 @@ async def test_azure_text_completion_call_with_parameters_logit_bias_not_none( endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) await azure_text_completion.complete_async(prompt, complete_request_settings) @@ -221,14 +206,12 @@ def test_azure_text_completion_serialize() -> None: api_key = "test_api_key" api_version = "2023-03-15-preview" default_headers = {"X-Test": "test"} - logger = Logger("test_logger") settings = { "deployment_name": deployment_name, "endpoint": endpoint, "api_key": api_key, "api_version": api_version, - "log": logger, "default_headers": default_headers, } diff --git a/python/tests/unit/ai/open_ai/services/test_azure_text_embedding.py b/python/tests/unit/ai/open_ai/services/test_azure_text_embedding.py index 40fb8686e7..0aa1bb9cde 100644 --- a/python/tests/unit/ai/open_ai/services/test_azure_text_embedding.py +++ b/python/tests/unit/ai/open_ai/services/test_azure_text_embedding.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger from unittest.mock import AsyncMock, call, patch import pytest @@ -22,7 +21,6 @@ def test_azure_text_embedding_init() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") # Test successful initialization azure_text_embedding = AzureTextEmbedding( @@ -30,7 +28,6 @@ def test_azure_text_embedding_init() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - logger=logger, ) assert azure_text_embedding.client is not None @@ -44,7 +41,6 @@ def test_azure_text_embedding_init_with_empty_deployment_name() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="ai_model_id"): AzureTextEmbedding( @@ -52,7 +48,6 @@ def test_azure_text_embedding_init_with_empty_deployment_name() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - logger=logger, ) @@ -61,7 +56,6 @@ def test_azure_text_embedding_init_with_empty_api_key() -> None: endpoint = "https://test-endpoint.com" # api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(AIException, match="api_key"): AzureTextEmbedding( @@ -69,7 +63,6 @@ def test_azure_text_embedding_init_with_empty_api_key() -> None: endpoint=endpoint, api_key="", api_version=api_version, - logger=logger, ) @@ -78,7 +71,6 @@ def test_azure_text_embedding_init_with_empty_endpoint() -> None: # endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="endpoint"): AzureTextEmbedding( @@ -86,7 +78,6 @@ def test_azure_text_embedding_init_with_empty_endpoint() -> None: endpoint="", api_key=api_key, api_version=api_version, - logger=logger, ) @@ -95,7 +86,6 @@ def test_azure_text_embedding_init_with_invalid_endpoint() -> None: endpoint = "http://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="https"): AzureTextEmbedding( @@ -103,7 +93,6 @@ def test_azure_text_embedding_init_with_invalid_endpoint() -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - logger=logger, ) @@ -112,7 +101,6 @@ def test_azure_text_embedding_init_with_from_dict() -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") default_headers = {"test_header": "test_value"} settings = { @@ -120,7 +108,6 @@ def test_azure_text_embedding_init_with_from_dict() -> None: "endpoint": endpoint, "api_key": api_key, "api_version": api_version, - "log": logger, "default_headers": default_headers, } @@ -146,7 +133,6 @@ async def test_azure_text_embedding_calls_with_parameters(mock_create) -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") texts = ["hello world", "goodbye world"] azure_text_embedding = AzureTextEmbedding( @@ -154,7 +140,6 @@ async def test_azure_text_embedding_calls_with_parameters(mock_create) -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - logger=logger, ) await azure_text_embedding.generate_embeddings_async(texts) @@ -172,7 +157,6 @@ async def test_azure_text_embedding_calls_with_batches(mock_create) -> None: endpoint = "https://test-endpoint.com" api_key = "test_api_key" api_version = "2023-03-15-preview" - logger = Logger("test_logger") texts = [i for i in range(0, 5)] azure_text_embedding = AzureTextEmbedding( @@ -180,7 +164,6 @@ async def test_azure_text_embedding_calls_with_batches(mock_create) -> None: endpoint=endpoint, api_key=api_key, api_version=api_version, - log=logger, ) await azure_text_embedding.generate_embeddings_async(texts, batch_size=3) diff --git a/python/tests/unit/ai/open_ai/services/test_openai_chat_completion.py b/python/tests/unit/ai/open_ai/services/test_openai_chat_completion.py index 50e8a50064..7df14f3013 100644 --- a/python/tests/unit/ai/open_ai/services/test_openai_chat_completion.py +++ b/python/tests/unit/ai/open_ai/services/test_openai_chat_completion.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger import pytest from pydantic import ValidationError @@ -17,13 +16,11 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion impo def test_open_ai_chat_completion_init() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") # Test successful initialization open_ai_chat_completion = OpenAIChatCompletion( ai_model_id=ai_model_id, api_key=api_key, - log=logger, ) assert open_ai_chat_completion.ai_model_id == ai_model_id @@ -33,14 +30,12 @@ def test_open_ai_chat_completion_init() -> None: def test_open_ai_chat_completion_init_with_default_header() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") default_headers = {"X-Unit-Test": "test-guid"} # Test successful initialization open_ai_chat_completion = OpenAIChatCompletion( ai_model_id=ai_model_id, api_key=api_key, - log=logger, default_headers=default_headers, ) @@ -56,39 +51,33 @@ def test_open_ai_chat_completion_init_with_default_header() -> None: def test_open_ai_chat_completion_init_with_empty_model_id() -> None: # ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="ai_model_id"): OpenAIChatCompletion( ai_model_id="", api_key=api_key, - log=logger, ) def test_open_ai_chat_completion_init_with_empty_api_key() -> None: ai_model_id = "test_model_id" # api_key = "test_api_key" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="api_key"): OpenAIChatCompletion( ai_model_id=ai_model_id, api_key="", - log=logger, ) def test_open_ai_chat_completion_serialize() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") default_headers = {"X-Unit-Test": "test-guid"} settings = { "ai_model_id": ai_model_id, "api_key": api_key, - "log": logger, "default_headers": default_headers, } @@ -108,13 +97,11 @@ def test_open_ai_chat_completion_serialize_with_org_id() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" org_id = "test_org_id" - logger = Logger("test_logger") settings = { "ai_model_id": ai_model_id, "api_key": api_key, "org_id": org_id, - "log": logger, } open_ai_chat_completion = OpenAIChatCompletion.from_dict(settings) diff --git a/python/tests/unit/ai/open_ai/services/test_openai_text_completion.py b/python/tests/unit/ai/open_ai/services/test_openai_text_completion.py index 99aa43b04c..78a30e6b22 100644 --- a/python/tests/unit/ai/open_ai/services/test_openai_text_completion.py +++ b/python/tests/unit/ai/open_ai/services/test_openai_text_completion.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger import pytest from pydantic import ValidationError @@ -14,13 +13,11 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion impo def test_open_ai_text_completion_init() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") # Test successful initialization open_ai_text_completion = OpenAITextCompletion( ai_model_id=ai_model_id, api_key=api_key, - log=logger, ) assert open_ai_text_completion.ai_model_id == ai_model_id @@ -30,14 +27,12 @@ def test_open_ai_text_completion_init() -> None: def test_open_ai_text_completion_init_with_default_header() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") default_headers = {"X-Unit-Test": "test-guid"} # Test successful initialization open_ai_text_completion = OpenAITextCompletion( ai_model_id=ai_model_id, api_key=api_key, - log=logger, default_headers=default_headers, ) @@ -51,39 +46,33 @@ def test_open_ai_text_completion_init_with_default_header() -> None: def test_open_ai_text_completion_init_with_empty_model_id() -> None: # ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="ai_model_id"): OpenAITextCompletion( ai_model_id="", api_key=api_key, - log=logger, ) def test_open_ai_text_completion_init_with_empty_api_key() -> None: ai_model_id = "test_model_id" # api_key = "test_api_key" - logger = Logger("test_logger") with pytest.raises(ValidationError, match="api_key"): OpenAITextCompletion( ai_model_id=ai_model_id, api_key="", - log=logger, ) def test_open_ai_text_completion_serialize() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" - logger = Logger("test_logger") default_headers = {"X-Unit-Test": "test-guid"} settings = { "ai_model_id": ai_model_id, "api_key": api_key, - "log": logger, "default_headers": default_headers, } @@ -101,13 +90,11 @@ def test_open_ai_text_completion_serialize_with_org_id() -> None: ai_model_id = "test_model_id" api_key = "test_api_key" org_id = "test_org_id" - logger = Logger("test_logger") settings = { "ai_model_id": ai_model_id, "api_key": api_key, "org_id": org_id, - "log": logger, } open_ai_text_completion = OpenAITextCompletion.from_dict(settings) diff --git a/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py b/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py index a9caf2c40a..41a71e0c4b 100644 --- a/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py +++ b/python/tests/unit/planning/sequential_planner/test_sequential_planner_extensions.py @@ -49,7 +49,7 @@ async def test_can_call_get_available_functions_with_no_functions_async(): memory.search_async.return_value = async_enumerable # Arrange GetAvailableFunctionsAsync parameters - context = SKContext(variables, memory, skills.read_only_skill_collection, Mock()) + context = SKContext(variables, memory, skills.read_only_skill_collection) config = SequentialPlannerConfig() semantic_query = "test" @@ -195,7 +195,6 @@ async def test_can_call_get_available_functions_with_functions_and_relevancy_asy memory=memory, skill_collection=skills, ) - context._logger = Mock() config = SequentialPlannerConfig(relevancy_threshold=0.78) semantic_query = "test" diff --git a/python/tests/unit/template_engine/blocks/test_block.py b/python/tests/unit/template_engine/blocks/test_block.py index fb4fb02a73..ca8168bf19 100644 --- a/python/tests/unit/template_engine/blocks/test_block.py +++ b/python/tests/unit/template_engine/blocks/test_block.py @@ -1,18 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger - from pytest import raises from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes -from semantic_kernel.utils.null_logger import NullLogger def test_init(): - block = Block(content="test content", log=NullLogger()) + block = Block(content="test content") assert block.content == "test content" - assert isinstance(block.log, Logger) def test_type_property(): diff --git a/python/tests/unit/template_engine/blocks/test_code_block.py b/python/tests/unit/template_engine/blocks/test_code_block.py index 46cecc1f84..858ca3c571 100644 --- a/python/tests/unit/template_engine/blocks/test_code_block.py +++ b/python/tests/unit/template_engine/blocks/test_code_block.py @@ -1,4 +1,3 @@ -from logging import Logger from unittest.mock import Mock from pytest import mark, raises @@ -21,7 +20,6 @@ from semantic_kernel.template_engine.blocks.var_block import VarBlock class TestCodeBlock: def setup_method(self): self.skills = Mock(spec=ReadOnlySkillCollectionBase) - self.log = Mock(spec=Logger) @mark.asyncio async def test_it_throws_if_a_function_doesnt_exist(self): @@ -29,11 +27,12 @@ class TestCodeBlock: variables=ContextVariables(), memory=NullMemory(), skill_collection=self.skills, - logger=self.log, ) # Make it so our self.skills mock's `has_function` method returns False self.skills.has_function.return_value = False - target = CodeBlock(content="functionName", log=self.log) + target = CodeBlock( + content="functionName", + ) with raises(ValueError): await target.render_code_async(context) @@ -44,7 +43,6 @@ class TestCodeBlock: variables=ContextVariables(), memory=NullMemory(), skill_collection=self.skills, - logger=self.log, ) def invoke(_): @@ -63,16 +61,28 @@ class TestCodeBlock: self.skills.has_function.return_value = True self.skills.get_function.return_value = function - target = CodeBlock(content="functionName", log=self.log) + target = CodeBlock( + content="functionName", + ) with raises(ValueError): await target.render_code_async(context) def test_it_has_the_correct_type(self): - assert CodeBlock(content="", log=self.log).type == BlockTypes.CODE + assert ( + CodeBlock( + content="", + ).type + == BlockTypes.CODE + ) def test_it_trims_spaces(self): - assert CodeBlock(content=" aa ", log=self.log).content == "aa" + assert ( + CodeBlock( + content=" aa ", + ).content + == "aa" + ) def test_it_checks_validity_of_internal_blocks(self): valid_block1 = FunctionIdBlock(content="x") @@ -81,10 +91,12 @@ class TestCodeBlock: invalid_block = VarBlock(content="!notvalid") code_block1 = CodeBlock( - tokens=[valid_block1, valid_block2], content="", log=self.log + tokens=[valid_block1, valid_block2], + content="", ) code_block2 = CodeBlock( - tokens=[valid_block1, invalid_block], content="", log=self.log + tokens=[valid_block1, invalid_block], + content="", ) is_valid1, _ = code_block1.is_valid() @@ -99,11 +111,21 @@ class TestCodeBlock: val_block = ValBlock(content="'value'") var_block = VarBlock(content="$var") - code_block1 = CodeBlock(tokens=[func_id, val_block], content="", log=self.log) - code_block2 = CodeBlock(tokens=[func_id, var_block], content="", log=self.log) - code_block3 = CodeBlock(tokens=[func_id, func_id], content="", log=self.log) + code_block1 = CodeBlock( + tokens=[func_id, val_block], + content="", + ) + code_block2 = CodeBlock( + tokens=[func_id, var_block], + content="", + ) + code_block3 = CodeBlock( + tokens=[func_id, func_id], + content="", + ) code_block4 = CodeBlock( - tokens=[func_id, var_block, var_block], content="", log=self.log + tokens=[func_id, var_block, var_block], + content="", ) is_valid1, _ = code_block1.is_valid() @@ -127,10 +149,11 @@ class TestCodeBlock: variables=variables, memory=NullMemory(), skill_collection=None, - logger=self.log, ) - code_block = CodeBlock(content="$varName", log=self.log) + code_block = CodeBlock( + content="$varName", + ) result = await code_block.render_code_async(context) assert result == "foo" @@ -144,11 +167,11 @@ class TestCodeBlock: variables=variables, memory=NullMemory(), skill_collection=None, - logger=self.log, ) code_block = CodeBlock( - tokens=[VarBlock(content="$varName")], content="", log=self.log + tokens=[VarBlock(content="$varName")], + content="", ) result = await code_block.render_code_async(context) @@ -160,10 +183,11 @@ class TestCodeBlock: variables=ContextVariables(), memory=NullMemory(), skill_collection=None, - logger=self.log, ) - code_block = CodeBlock(content="'ciao'", log=self.log) + code_block = CodeBlock( + content="'ciao'", + ) result = await code_block.render_code_async(context) assert result == "ciao" @@ -174,11 +198,11 @@ class TestCodeBlock: variables=ContextVariables(), memory=NullMemory(), skill_collection=None, - logger=self.log, ) code_block = CodeBlock( - tokens=[ValBlock(content="'arrivederci'")], content="", log=self.log + tokens=[ValBlock(content="'arrivederci'")], + content="", ) result = await code_block.render_code_async(context) @@ -192,12 +216,11 @@ class TestCodeBlock: variables["var1"] = "uno" variables["var2"] = "due" - # Create a context with the variables, memory, skill collection, and logger + # Create a context with the variables, memory, and skill collection context = SKContext.model_construct( variables=variables, memory=NullMemory(), skill_collection=self.skills, - logger=self.log, ) # Create a FunctionIdBlock with the function name @@ -234,7 +257,10 @@ class TestCodeBlock: self.skills.get_function.return_value = function # Create a CodeBlock with the FunctionIdBlock and render it with the context - code_block = CodeBlock(tokens=[func_id], content="", log=self.log) + code_block = CodeBlock( + tokens=[func_id], + content="", + ) await code_block.render_code_async(context) # Check that the canary values match the original context variables @@ -257,12 +283,11 @@ class TestCodeBlock: variables = ContextVariables() variables[VAR_NAME] = VAR_VALUE - # Create a context with the variables, memory, skill collection, and logger + # Create a context with the variables, memory, and skill collection context = SKContext.model_construct( variables=variables, memory=NullMemory(), skill_collection=self.skills, - logger=self.log, ) # Create a FunctionIdBlock with the function name and a @@ -295,7 +320,10 @@ class TestCodeBlock: # Create a CodeBlock with the FunctionIdBlock and VarBlock, # and render it with the context - code_block = CodeBlock(tokens=[func_id, var_block], content="", log=self.log) + code_block = CodeBlock( + tokens=[func_id, var_block], + content="", + ) result = await code_block.render_code_async(context) # Check that the result matches the custom variable value @@ -308,12 +336,11 @@ class TestCodeBlock: # Define a value to be used in the test VALUE = "value" - # Create a context with empty variables, memory, skill collection, and logger + # Create a context with empty variables, memory, and skill collection context = SKContext.model_construct( variables=ContextVariables(), memory=NullMemory(), skill_collection=self.skills, - logger=self.log, ) # Create a FunctionIdBlock with the function name and a ValBlock with the value @@ -345,7 +372,10 @@ class TestCodeBlock: # Create a CodeBlock with the FunctionIdBlock and ValBlock, # and render it with the context - code_block = CodeBlock(tokens=[func_id, val_block], content="", log=self.log) + code_block = CodeBlock( + tokens=[func_id, val_block], + content="", + ) result = await code_block.render_code_async(context) # Check that the result matches the value diff --git a/python/tests/unit/template_engine/blocks/test_function_id_block.py b/python/tests/unit/template_engine/blocks/test_function_id_block.py index 004ad5343a..d7b7c654c8 100644 --- a/python/tests/unit/template_engine/blocks/test_function_id_block.py +++ b/python/tests/unit/template_engine/blocks/test_function_id_block.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger from pytest import mark, raises @@ -10,11 +9,8 @@ from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdB def test_init(): - function_id_block = FunctionIdBlock( - content="skill.function", log=Logger("test_logger") - ) + function_id_block = FunctionIdBlock(content="skill.function") assert function_id_block.content == "skill.function" - assert isinstance(function_id_block.log, Logger) def test_type_property(): diff --git a/python/tests/unit/template_engine/blocks/test_text_block.py b/python/tests/unit/template_engine/blocks/test_text_block.py index aff0a97244..03cb3f3728 100644 --- a/python/tests/unit/template_engine/blocks/test_text_block.py +++ b/python/tests/unit/template_engine/blocks/test_text_block.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger from pytest import raises @@ -10,53 +9,37 @@ from semantic_kernel.template_engine.blocks.text_block import TextBlock def test_init(): - text_block = TextBlock.from_text(text="test text", log=Logger("test_logger")) + text_block = TextBlock.from_text(text="test text") assert text_block.content == "test text" - assert isinstance(text_block.log, Logger) def test_init_with_just_start_index(): - text_block = TextBlock.from_text( - text="test text", start_index=2, log=Logger("test_logger") - ) + text_block = TextBlock.from_text(text="test text", start_index=2) assert text_block.content == "st text" - assert isinstance(text_block.log, Logger) def test_init_with_just_stop_index(): - text_block = TextBlock.from_text( - text="test text", stop_index=2, log=Logger("test_logger") - ) + text_block = TextBlock.from_text(text="test text", stop_index=2) assert text_block.content == "te" - assert isinstance(text_block.log, Logger) def test_init_with_start_index_greater_than_stop_index(): with raises(ValueError): - TextBlock.from_text( - text="test text", start_index=2, stop_index=1, log=Logger("test_logger") - ) + TextBlock.from_text(text="test text", start_index=2, stop_index=1) def test_init_with_start_stop_indices(): - text_block = TextBlock.from_text( - text="test text", start_index=0, stop_index=4, log=Logger("test_logger") - ) + text_block = TextBlock.from_text(text="test text", start_index=0, stop_index=4) assert text_block.content == "test" - assert isinstance(text_block.log, Logger) def test_init_with_start_index_less_than_zero(): with raises(ValueError): - TextBlock.from_text( - text="test text", start_index=-1, stop_index=1, log=Logger("test_logger") - ) + TextBlock.from_text(text="test text", start_index=-1, stop_index=1) def test_init_with_negative_stop_index(): - text_block = TextBlock.from_text( - text="test text", stop_index=-1, log=Logger("test_logger") - ) + text_block = TextBlock.from_text(text="test text", stop_index=-1) assert text_block.content == "test tex" diff --git a/python/tests/unit/template_engine/blocks/test_val_block.py b/python/tests/unit/template_engine/blocks/test_val_block.py index 7ce2fae490..7dc55b2f72 100644 --- a/python/tests/unit/template_engine/blocks/test_val_block.py +++ b/python/tests/unit/template_engine/blocks/test_val_block.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger from semantic_kernel.orchestration.context_variables import ContextVariables from semantic_kernel.template_engine.blocks.block_types import BlockTypes @@ -8,9 +7,8 @@ from semantic_kernel.template_engine.blocks.val_block import ValBlock def test_init(): - val_block = ValBlock(content="'test value'", log=Logger("test_logger")) + val_block = ValBlock(content="'test value'") assert val_block.content == "'test value'" - assert isinstance(val_block.log, Logger) def test_type_property(): diff --git a/python/tests/unit/template_engine/blocks/test_var_block.py b/python/tests/unit/template_engine/blocks/test_var_block.py index a08b63d2a7..840a18c527 100644 --- a/python/tests/unit/template_engine/blocks/test_var_block.py +++ b/python/tests/unit/template_engine/blocks/test_var_block.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from logging import Logger from pytest import mark, raises @@ -11,9 +10,8 @@ from semantic_kernel.template_engine.blocks.var_block import VarBlock def test_init(): - var_block = VarBlock(content="$test_var", log=Logger("test_logger")) + var_block = VarBlock(content="$test_var") assert var_block.content == "$test_var" - assert isinstance(var_block.log, Logger) def test_type_property(): diff --git a/python/tests/unit/template_engine/test_prompt_template_engine.py b/python/tests/unit/template_engine/test_prompt_template_engine.py index 5f5487f60b..76366569a1 100644 --- a/python/tests/unit/template_engine/test_prompt_template_engine.py +++ b/python/tests/unit/template_engine/test_prompt_template_engine.py @@ -14,7 +14,6 @@ from semantic_kernel.skill_definition.read_only_skill_collection import ( ) from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.prompt_template_engine import PromptTemplateEngine -from semantic_kernel.utils.null_logger import NullLogger @fixture @@ -34,7 +33,7 @@ def skills(): @fixture def context(variables, skills): - return SKContext(variables, NullMemory(), skills, NullLogger()) + return SKContext(variables, NullMemory(), skills) def test_it_renders_variables( diff --git a/python/tests/unit/test_serialization.py b/python/tests/unit/test_serialization.py index 5a72cdf3ca..c6acdf2838 100644 --- a/python/tests/unit/test_serialization.py +++ b/python/tests/unit/test_serialization.py @@ -1,4 +1,3 @@ -import logging import typing as t import pytest @@ -126,9 +125,9 @@ def sk_factory() -> t.Callable[[t.Type[_Serializable]], _Serializable]: TextBlock: TextBlock(content="baz"), ValBlock: ValBlock(content="qux"), VarBlock: VarBlock(content="quux"), - CodeTokenizer: CodeTokenizer(log=logging.getLogger("test")), - PromptTemplateEngine: PromptTemplateEngine(logger=logging.getLogger("test")), - TemplateTokenizer: TemplateTokenizer(log=logging.getLogger("test")), + CodeTokenizer: CodeTokenizer(), + PromptTemplateEngine: PromptTemplateEngine(), + TemplateTokenizer: TemplateTokenizer(), ParameterView: ParameterView( name="foo", description="bar",