Skip to content

Commit 016cd33

Browse files
Jordan Henkeldluc
authored andcommitted
Python: Upgrade PromptTemplateEngine (bring back to parity w/ C# version) (microsoft#200)
### Motivation and Context The C# Semantic Kernel has recently undergone an upgrade to its `PromptTemplateEngine`. This brings Python back in line with the semantics of Prompt Templates in C#. ### Description Here, unlike the original port, I've tried to make things more pythonic/idiomatic (instead of trying to directly mirror the C# codebase). I've also brought over the corresponding unit tests (and added some of my own as I was building/validating).
1 parent 3e3745a commit 016cd33

37 files changed

Lines changed: 2629 additions & 440 deletions

python/semantic_kernel/kernel.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,22 @@
3434
)
3535
from semantic_kernel.skill_definition.skill_collection import SkillCollection
3636
from semantic_kernel.skill_definition.skill_collection_base import SkillCollectionBase
37-
from semantic_kernel.template_engine.prompt_template_engine_base import (
38-
PromptTemplateEngineBase,
37+
from semantic_kernel.template_engine.protocols.prompt_templating_engine import (
38+
PromptTemplatingEngine,
3939
)
4040

4141

4242
class Kernel(KernelBase):
4343
_log: Logger
4444
_config: KernelConfig
4545
_skill_collection: SkillCollectionBase
46-
_prompt_template_engine: PromptTemplateEngineBase
46+
_prompt_template_engine: PromptTemplatingEngine
4747
_memory: SemanticTextMemoryBase
4848

4949
def __init__(
5050
self,
5151
skill_collection: SkillCollectionBase,
52-
prompt_template_engine: PromptTemplateEngineBase,
52+
prompt_template_engine: PromptTemplatingEngine,
5353
memory: SemanticTextMemoryBase,
5454
config: KernelConfig,
5555
log: Logger,
@@ -73,7 +73,7 @@ def memory(self) -> SemanticTextMemoryBase:
7373
return self._memory
7474

7575
@property
76-
def prompt_template_engine(self) -> PromptTemplateEngineBase:
76+
def prompt_template_engine(self) -> PromptTemplatingEngine:
7777
return self._prompt_template_engine
7878

7979
@property

python/semantic_kernel/kernel_base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
1616
ReadOnlySkillCollectionBase,
1717
)
18-
from semantic_kernel.template_engine.prompt_template_engine_base import (
19-
PromptTemplateEngineBase,
18+
from semantic_kernel.template_engine.protocols.prompt_templating_engine import (
19+
PromptTemplatingEngine,
2020
)
2121

2222

@@ -38,7 +38,7 @@ def memory(self) -> SemanticTextMemoryBase:
3838

3939
@property
4040
@abstractmethod
41-
def prompt_template_engine(self) -> PromptTemplateEngineBase:
41+
def prompt_template_engine(self) -> PromptTemplatingEngine:
4242
pass
4343

4444
@property

python/semantic_kernel/orchestration/context_variables.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77

88

99
class ContextVariables:
10-
_variables: Dict[str, str] = {}
11-
_main_key = "input"
12-
13-
def __init__(self, content: str = "") -> None:
10+
def __init__(self, content: str = "", variables: Dict[str, str] = None) -> None:
11+
"""
12+
Initialize the ContextVariables instance with an optional content string.
13+
14+
:param content: The content string to be stored as the main variable,
15+
defaults to an empty string.
16+
"""
17+
self._variables: Dict[str, str] = variables or {}
18+
self._main_key: str = "input"
1419
self._variables[self._main_key] = content
1520

1621
@property

python/semantic_kernel/semantic_functions/chat_prompt_template.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from semantic_kernel.semantic_functions.prompt_template_config import (
88
PromptTemplateConfig,
99
)
10-
from semantic_kernel.template_engine.prompt_template_engine_base import (
11-
PromptTemplateEngineBase,
10+
from semantic_kernel.template_engine.protocols.prompt_templating_engine import (
11+
PromptTemplatingEngine,
1212
)
1313

1414
if TYPE_CHECKING:
@@ -21,7 +21,7 @@ class ChatPromptTemplate(PromptTemplate):
2121
def __init__(
2222
self,
2323
template: str,
24-
template_engine: PromptTemplateEngineBase,
24+
template_engine: PromptTemplatingEngine,
2525
prompt_config: PromptTemplateConfig,
2626
log: Optional[Logger] = None,
2727
) -> None:

python/semantic_kernel/semantic_functions/prompt_template.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from semantic_kernel.skill_definition.parameter_view import ParameterView
1111
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
1212
from semantic_kernel.template_engine.blocks.var_block import VarBlock
13-
from semantic_kernel.template_engine.prompt_template_engine_base import (
14-
PromptTemplateEngineBase,
13+
from semantic_kernel.template_engine.protocols.prompt_templating_engine import (
14+
PromptTemplatingEngine,
1515
)
1616
from semantic_kernel.utils.null_logger import NullLogger
1717

@@ -21,14 +21,14 @@
2121

2222
class PromptTemplate(PromptTemplateBase):
2323
_template: str
24-
_template_engine: PromptTemplateEngineBase
24+
_template_engine: PromptTemplatingEngine
2525
_log: Logger
2626
_prompt_config: PromptTemplateConfig
2727

2828
def __init__(
2929
self,
3030
template: str,
31-
template_engine: PromptTemplateEngineBase,
31+
template_engine: PromptTemplatingEngine,
3232
prompt_config: PromptTemplateConfig,
3333
log: Optional[Logger] = None,
3434
) -> None:
@@ -53,7 +53,7 @@ def get_parameters(self) -> List[ParameterView]:
5353

5454
blocks = self._template_engine.extract_blocks(self._template)
5555
for block in blocks:
56-
if block.type != BlockTypes.Variable:
56+
if block.type != BlockTypes.VARIABLE:
5757
continue
5858
if block is None:
5959
continue

python/semantic_kernel/skill_definition/read_only_skill_collection.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,8 @@ def get_functions_view(
4949
return self._skill_collection.get_functions_view(
5050
include_semantic, include_native
5151
)
52+
53+
def get_function(
54+
self, skill_name: Optional[str], function_name: str
55+
) -> "SKFunctionBase":
56+
return self._skill_collection.get_function(skill_name, function_name)

python/semantic_kernel/skill_definition/read_only_skill_collection_base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,9 @@ def get_functions_view(
4242
self, include_semantic: bool = True, include_native: bool = True
4343
) -> "FunctionsView":
4444
pass
45+
46+
@abstractmethod
47+
def get_function(
48+
self, skill_name: Optional[str], function_name: str
49+
) -> "SKFunctionBase":
50+
pass

python/semantic_kernel/skill_definition/skill_collection.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ def get_functions_view(
118118

119119
return result
120120

121+
def get_function(
122+
self, skill_name: Optional[str], function_name: str
123+
) -> "SKFunctionBase":
124+
s_name, f_name = self._normalize_names(skill_name, function_name, True)
125+
if self.has_function(s_name, f_name):
126+
return self._skill_collection[s_name][f_name]
127+
128+
self._log.error(f"Function not available: {s_name}.{f_name}")
129+
raise KernelException(
130+
KernelException.ErrorCodes.FunctionNotAvailable,
131+
f"Function not available: {s_name}.{f_name}",
132+
)
133+
121134
def _normalize_names(
122135
self,
123136
skill_name: Optional[str],
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Prompt Template Engine
2+
3+
The Semantic Kernel uses the following grammar to parse prompt templates:
4+
5+
```
6+
# BNF parsed by TemplateTokenizer
7+
[template] ::= "" | [block] | [block] [template]
8+
[block] ::= [sk-block] | [text-block]
9+
[sk-block] ::= "{{" [variable] "}}" | "{{" [value] "}}" | "{{" [function-call] "}}"
10+
[text-block] ::= [any-char] | [any-char] [text-block]
11+
[any-char] ::= any char
12+
13+
# BNF parsed by CodeTokenizer:
14+
[template] ::= "" | [variable] " " [template] | [value] " " [template] | [function-call] " " [template]
15+
[variable] ::= "$" [valid-name]
16+
[value] ::= "'" [text] "'" | '"' [text] '"'
17+
[function-call] ::= [function-id] | [function-id] [parameter]
18+
[parameter] ::= [variable] | [value]
19+
20+
# BNF parsed by dedicated blocks
21+
[function-id] ::= [valid-name] | [valid-name] "." [valid-name]
22+
[valid-name] ::= [valid-symbol] | [valid-symbol] [valid-name]
23+
[valid-symbol] ::= [letter] | [digit] | "_"
24+
[letter] ::= "a" | "b" ... | "z" | "A" | "B" ... | "Z"
25+
[digit] ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
26+
```
Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,19 @@
11
# Copyright (c) Microsoft. All rights reserved.
22

3-
from abc import ABC, abstractmethod
43
from logging import Logger
54
from typing import Optional, Tuple
65

7-
from semantic_kernel.orchestration.context_variables import ContextVariables
8-
from semantic_kernel.orchestration.sk_context import SKContext
96
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
7+
from semantic_kernel.utils.null_logger import NullLogger
108

119

12-
class Block(ABC):
13-
_type: BlockTypes
14-
_content: str
15-
_log: Logger
16-
17-
def __init__(self, block_type: BlockTypes, content: str, log: Logger) -> None:
18-
self._type = block_type
19-
self._content = content
20-
self._log = log
21-
22-
async def render_code_async(self, context: SKContext) -> str:
23-
raise NotImplementedError("This block does not support code execution")
24-
25-
@abstractmethod
26-
def is_valid(self) -> Tuple[bool, str]:
27-
pass
28-
29-
@abstractmethod
30-
def render(self, variables: Optional[ContextVariables]) -> str:
31-
pass
10+
class Block:
11+
def __init__(
12+
self, content: Optional[str] = None, log: Optional[Logger] = NullLogger
13+
) -> None:
14+
self._content = content or ""
15+
self._log = log or NullLogger()
16+
self._type = BlockTypes.UNDEFINED
3217

3318
@property
3419
def type(self) -> BlockTypes:
@@ -37,3 +22,10 @@ def type(self) -> BlockTypes:
3722
@property
3823
def content(self) -> str:
3924
return self._content
25+
26+
@property
27+
def log(self) -> Logger:
28+
return self._log
29+
30+
def is_valid(self) -> Tuple[bool, str]:
31+
raise NotImplementedError("Subclasses must implement this method.")

0 commit comments

Comments
 (0)