Skip to content

Commit 493dfc1

Browse files
Python: Merge Kernel, KernelBase, KernelExtensions (microsoft#1015)
### Motivation and Context The separation of Kernel, KernelBase, and KernelExtensions is several layers of unnecessary abstraction. This PR does not change any API or kernel behavior - it just reduces the number of files and namespaces to accomplish the same functionality. ### Description - Removed the `semantic_kernel/kernel_extensions` namespace. Methods that existed in this namespace now exist in kernel.py. - Removed KernelBase - this abstract class implies that there are multiple ways to instantiate SK. For Python, we expect only a single type of Kernel to instantiated for all instances. - Updated tests and imports to accommodate the above changes.
1 parent 7066a4c commit 493dfc1

9 files changed

Lines changed: 177 additions & 265 deletions

File tree

python/semantic_kernel/kernel.py

Lines changed: 174 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Copyright (c) Microsoft. All rights reserved.
22

3+
import glob
4+
import importlib
35
import inspect
6+
import os
47
from logging import Logger
58
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union
9+
from uuid import uuid4
610

711
from semantic_kernel.connectors.ai.ai_exception import AIException
812
from semantic_kernel.connectors.ai.chat_completion_client_base import (
@@ -18,11 +22,10 @@
1822
from semantic_kernel.connectors.ai.text_completion_client_base import (
1923
TextCompletionClientBase,
2024
)
21-
from semantic_kernel.kernel_base import KernelBase
2225
from semantic_kernel.kernel_exception import KernelException
23-
from semantic_kernel.kernel_extensions import KernelExtensions
2426
from semantic_kernel.memory.memory_store_base import MemoryStoreBase
2527
from semantic_kernel.memory.null_memory import NullMemory
28+
from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory
2629
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
2730
from semantic_kernel.orchestration.context_variables import ContextVariables
2831
from semantic_kernel.orchestration.sk_context import SKContext
@@ -32,6 +35,10 @@
3235
PassThroughWithoutRetry,
3336
)
3437
from semantic_kernel.reliability.retry_mechanism import RetryMechanism
38+
from semantic_kernel.semantic_functions.prompt_template import PromptTemplate
39+
from semantic_kernel.semantic_functions.prompt_template_config import (
40+
PromptTemplateConfig,
41+
)
3542
from semantic_kernel.semantic_functions.semantic_function_config import (
3643
SemanticFunctionConfig,
3744
)
@@ -50,7 +57,7 @@
5057
T = TypeVar("T")
5158

5259

53-
class Kernel(KernelBase, KernelExtensions):
60+
class Kernel:
5461
_log: Logger
5562
_skill_collection: SkillCollectionBase
5663
_prompt_template_engine: PromptTemplatingEngine
@@ -75,13 +82,13 @@ def __init__(
7582
self._memory = memory if memory else NullMemory()
7683

7784
self._text_completion_services: Dict[
78-
str, Callable[["KernelBase"], TextCompletionClientBase]
85+
str, Callable[["Kernel"], TextCompletionClientBase]
7986
] = {}
8087
self._chat_services: Dict[
81-
str, Callable[["KernelBase"], ChatCompletionClientBase]
88+
str, Callable[["Kernel"], ChatCompletionClientBase]
8289
] = {}
8390
self._text_embedding_generation_services: Dict[
84-
str, Callable[["KernelBase"], EmbeddingGeneratorBase]
91+
str, Callable[["Kernel"], EmbeddingGeneratorBase]
8592
] = {}
8693

8794
self._default_text_completion_service: Optional[str] = None
@@ -90,9 +97,6 @@ def __init__(
9097

9198
self._retry_mechanism: RetryMechanism = PassThroughWithoutRetry()
9299

93-
def kernel(self) -> KernelBase:
94-
return self
95-
96100
@property
97101
def logger(self) -> Logger:
98102
return self._log
@@ -212,6 +216,29 @@ def func(self, skill_name: str, function_name: str) -> SKFunctionBase:
212216

213217
return self.skills.get_semantic_function(skill_name, function_name)
214218

219+
def use_memory(
220+
self,
221+
storage: MemoryStoreBase,
222+
embeddings_generator: Optional[EmbeddingGeneratorBase] = None,
223+
) -> None:
224+
if embeddings_generator is None:
225+
service_id = self.get_text_embedding_generation_service_id()
226+
if not service_id:
227+
raise ValueError("The embedding service id cannot be `None` or empty")
228+
229+
embeddings_service = self.get_ai_service(EmbeddingGeneratorBase, service_id)
230+
if not embeddings_service:
231+
raise ValueError(f"AI configuration is missing for: {service_id}")
232+
233+
embeddings_generator = embeddings_service(self)
234+
235+
if storage is None:
236+
raise ValueError("The storage instance provided cannot be `None`")
237+
if embeddings_generator is None:
238+
raise ValueError("The embedding generator cannot be `None`")
239+
240+
self.register_memory(SemanticTextMemory(storage, embeddings_generator))
241+
215242
def register_memory(self, memory: SemanticTextMemoryBase) -> None:
216243
self._memory = memory
217244

@@ -267,7 +294,7 @@ def import_skill(
267294

268295
def get_ai_service(
269296
self, type: Type[T], service_id: Optional[str] = None
270-
) -> Callable[["KernelBase"], T]:
297+
) -> Callable[["Kernel"], T]:
271298
matching_type = {}
272299
if type == TextCompletionClientBase:
273300
service_id = service_id or self._default_text_completion_service
@@ -301,7 +328,7 @@ def add_text_completion_service(
301328
self,
302329
service_id: str,
303330
service: Union[
304-
TextCompletionClientBase, Callable[["KernelBase"], TextCompletionClientBase]
331+
TextCompletionClientBase, Callable[["Kernel"], TextCompletionClientBase]
305332
],
306333
overwrite: bool = True,
307334
) -> "Kernel":
@@ -324,7 +351,7 @@ def add_chat_service(
324351
self,
325352
service_id: str,
326353
service: Union[
327-
ChatCompletionClientBase, Callable[["KernelBase"], ChatCompletionClientBase]
354+
ChatCompletionClientBase, Callable[["Kernel"], ChatCompletionClientBase]
328355
],
329356
overwrite: bool = True,
330357
) -> "Kernel":
@@ -352,7 +379,7 @@ def add_text_embedding_generation_service(
352379
self,
353380
service_id: str,
354381
service: Union[
355-
EmbeddingGeneratorBase, Callable[["KernelBase"], EmbeddingGeneratorBase]
382+
EmbeddingGeneratorBase, Callable[["Kernel"], EmbeddingGeneratorBase]
356383
],
357384
overwrite: bool = False,
358385
) -> "Kernel":
@@ -567,3 +594,137 @@ def _create_semantic_function(
567594
function.set_ai_service(lambda: service(self))
568595

569596
return function
597+
598+
def import_native_skill_from_directory(
599+
self, parent_directory: str, skill_directory_name: str
600+
) -> Dict[str, SKFunctionBase]:
601+
MODULE_NAME = "native_function"
602+
603+
validate_skill_name(skill_directory_name)
604+
605+
skill_directory = os.path.abspath(
606+
os.path.join(parent_directory, skill_directory_name)
607+
)
608+
native_py_file_path = os.path.join(skill_directory, f"{MODULE_NAME}.py")
609+
610+
if not os.path.exists(native_py_file_path):
611+
raise ValueError(
612+
f"Native Skill Python File does not exist: {native_py_file_path}"
613+
)
614+
615+
skill_name = os.path.basename(skill_directory)
616+
try:
617+
spec = importlib.util.spec_from_file_location(
618+
MODULE_NAME, native_py_file_path
619+
)
620+
module = importlib.util.module_from_spec(spec)
621+
spec.loader.exec_module(module)
622+
623+
class_name = next(
624+
(
625+
name
626+
for name, cls in inspect.getmembers(module, inspect.isclass)
627+
if cls.__module__ == MODULE_NAME
628+
),
629+
None,
630+
)
631+
if class_name:
632+
skill_obj = getattr(module, class_name)()
633+
return self.import_skill(skill_obj, skill_name)
634+
except Exception:
635+
pass
636+
637+
return {}
638+
639+
def import_semantic_skill_from_directory(
640+
self, parent_directory: str, skill_directory_name: str
641+
) -> Dict[str, SKFunctionBase]:
642+
CONFIG_FILE = "config.json"
643+
PROMPT_FILE = "skprompt.txt"
644+
645+
validate_skill_name(skill_directory_name)
646+
647+
skill_directory = os.path.join(parent_directory, skill_directory_name)
648+
skill_directory = os.path.abspath(skill_directory)
649+
650+
if not os.path.exists(skill_directory):
651+
raise ValueError(f"Skill directory does not exist: {skill_directory_name}")
652+
653+
skill = {}
654+
655+
directories = glob.glob(skill_directory + "/*/")
656+
for directory in directories:
657+
dir_name = os.path.dirname(directory)
658+
function_name = os.path.basename(dir_name)
659+
prompt_path = os.path.join(directory, PROMPT_FILE)
660+
661+
# Continue only if the prompt template exists
662+
if not os.path.exists(prompt_path):
663+
continue
664+
665+
config = PromptTemplateConfig()
666+
config_path = os.path.join(directory, CONFIG_FILE)
667+
with open(config_path, "r") as config_file:
668+
config = config.from_json(config_file.read())
669+
670+
# Load Prompt Template
671+
with open(prompt_path, "r") as prompt_file:
672+
template = PromptTemplate(
673+
prompt_file.read(), self.prompt_template_engine, config
674+
)
675+
676+
# Prepare lambda wrapping AI logic
677+
function_config = SemanticFunctionConfig(config, template)
678+
679+
skill[function_name] = self.register_semantic_function(
680+
skill_directory_name, function_name, function_config
681+
)
682+
683+
return skill
684+
685+
def create_semantic_function(
686+
self,
687+
prompt_template: str,
688+
function_name: Optional[str] = None,
689+
skill_name: Optional[str] = None,
690+
description: Optional[str] = None,
691+
max_tokens: int = 256,
692+
temperature: float = 0.0,
693+
top_p: float = 1.0,
694+
presence_penalty: float = 0.0,
695+
frequency_penalty: float = 0.0,
696+
stop_sequences: Optional[List[str]] = None,
697+
) -> "SKFunctionBase":
698+
function_name = (
699+
function_name
700+
if function_name is not None
701+
else f"f_{str(uuid4()).replace('-', '_')}"
702+
)
703+
704+
config = PromptTemplateConfig(
705+
description=(
706+
description
707+
if description is not None
708+
else "Generic function, unknown purpose"
709+
),
710+
type="completion",
711+
completion=PromptTemplateConfig.CompletionConfig(
712+
temperature,
713+
top_p,
714+
presence_penalty,
715+
frequency_penalty,
716+
max_tokens,
717+
stop_sequences if stop_sequences is not None else [],
718+
),
719+
)
720+
721+
validate_function_name(function_name)
722+
if skill_name is not None:
723+
validate_skill_name(skill_name)
724+
725+
template = PromptTemplate(prompt_template, self.prompt_template_engine, config)
726+
function_config = SemanticFunctionConfig(config, template)
727+
728+
return self.register_semantic_function(
729+
skill_name, function_name, function_config
730+
)

python/semantic_kernel/kernel_extensions/__init__.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

python/semantic_kernel/kernel_extensions/extends_kernel.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)