Python: Uses Python standard logging (#4115)

### Motivation and Context

This replaces passing Logger instances as arguments around with the
preferred Python way of having each module define a module-wide `logger`
instance at the top of the module. Fixes #3711

### Description

Removes any `log` argument and argument documentation. Replaces usage of
those `log` arguments with the module-wide `logger` instance.

### Contribution Checklist

- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone 😄

---------

Co-authored-by: Eduard van Valkenburg <[email protected]>
This commit is contained in:
Julio Carlos Menendez
2023-12-16 18:41:41 +00:00
committed by GitHub
co-authored by Eduard van Valkenburg
parent 24a099965e
commit d3dd5c8e7e
78 changed files with 913 additions and 788 deletions
@@ -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",
@@ -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())
+2
View File
@@ -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",
]
@@ -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
@@ -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.
@@ -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"
)
@@ -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"
)
@@ -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,
@@ -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:
@@ -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,
@@ -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).
@@ -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,
)
@@ -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"),
)
@@ -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"),
)
@@ -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"),
)
@@ -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
)
@@ -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,
)
@@ -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)
@@ -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"),
)
@@ -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
)
@@ -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"),
)
@@ -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:
@@ -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.
@@ -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
@@ -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."
)
@@ -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."
@@ -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}
@@ -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,
@@ -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
@@ -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,
@@ -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:
@@ -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
@@ -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
)
@@ -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()
@@ -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)
@@ -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 []
@@ -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 []
@@ -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])
+23 -31
View File
@@ -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]
@@ -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."
)
@@ -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.
@@ -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
@@ -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.
@@ -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}."
+15 -9
View File
@@ -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
@@ -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(
@@ -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}",
)
@@ -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
@@ -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
@@ -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,
@@ -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()
@@ -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}",
@@ -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:
@@ -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
@@ -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
@@ -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"
@@ -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:
@@ -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, ""
@@ -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 ""
@@ -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")
@@ -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
@@ -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
+11
View File
@@ -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",
)
+9 -1
View File
@@ -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"]
@@ -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,
}
@@ -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,
}
@@ -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)
@@ -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)
@@ -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)
@@ -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"
@@ -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():
@@ -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
@@ -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():
@@ -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"
@@ -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():
@@ -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():
@@ -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(
+3 -4
View File
@@ -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",