Skip to content

Commit 0a1a7ca

Browse files
authored
Python: support Jinja2 templating (microsoft#5483)
### Motivation and Context Jinja2 is a heavily supported Python templating language and we're introducing it to SK Python. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description This PR brings in the ability to specify Jinja2 templates for use within SK Python: - Closes microsoft#5343 <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [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 😄
1 parent 043e49b commit 0a1a7ca

20 files changed

Lines changed: 939 additions & 63 deletions

‎python/notebooks/05-using-the-planner.ipynb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"metadata": {},
2424
"outputs": [],
2525
"source": [
26-
"!python -m pip install -U semantic-kernel "
26+
"!python -m pip install -U semantic-kernel"
2727
]
2828
},
2929
{

‎python/notebooks/06-memory-and-embeddings.ipynb‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -349,18 +349,18 @@
349349
"outputs": [],
350350
"source": [
351351
"github_files = {}\n",
352-
"github_files[\n",
353-
" \"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"\n",
354-
"] = \"README: Installation, getting started, and how to contribute\"\n",
352+
"github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"] = (\n",
353+
" \"README: Installation, getting started, and how to contribute\"\n",
354+
")\n",
355355
"github_files[\n",
356356
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"\n",
357357
"] = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\"\n",
358-
"github_files[\n",
359-
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"\n",
360-
"] = \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n",
361-
"github_files[\n",
362-
" \"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"\n",
363-
"] = \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n",
358+
"github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"] = (\n",
359+
" \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n",
360+
")\n",
361+
"github_files[\"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"] = (\n",
362+
" \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n",
363+
")\n",
364364
"github_files[\n",
365365
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs\"\n",
366366
"] = \"C# class that defines a volatile embedding store\""

‎python/notebooks/third_party/weaviate-persistent-memory.ipynb‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -422,18 +422,18 @@
422422
"outputs": [],
423423
"source": [
424424
"github_files = {}\n",
425-
"github_files[\n",
426-
" \"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"\n",
427-
"] = \"README: Installation, getting started, and how to contribute\"\n",
425+
"github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"] = (\n",
426+
" \"README: Installation, getting started, and how to contribute\"\n",
427+
")\n",
428428
"github_files[\n",
429429
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"\n",
430430
"] = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\"\n",
431-
"github_files[\n",
432-
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"\n",
433-
"] = \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n",
434-
"github_files[\n",
435-
" \"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"\n",
436-
"] = \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n",
431+
"github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"] = (\n",
432+
" \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n",
433+
")\n",
434+
"github_files[\"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"] = (\n",
435+
" \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n",
436+
")\n",
437437
"github_files[\n",
438438
" \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs\"\n",
439439
"] = \"C# class that defines a volatile embedding store\""

‎python/poetry.lock‎

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎python/pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pydantic = "^2"
3232
motor = "^3.3.2"
3333
defusedxml = "^0.7.1"
3434
pybars4 = "^0.9.13"
35+
jinja2 = "^3.1.3"
3536

3637
# Optional dependencies
3738
ipykernel = { version = "^6.21.1", optional = true}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
import logging
5+
6+
import semantic_kernel as sk
7+
import semantic_kernel.connectors.ai.open_ai as sk_oai
8+
from semantic_kernel.contents.chat_history import ChatHistory
9+
from semantic_kernel.functions.kernel_arguments import KernelArguments
10+
from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env_as_dict
11+
12+
logging.basicConfig(level=logging.WARNING)
13+
14+
system_message = """
15+
You are a chat bot. Your name is Mosscap and
16+
you have one goal: figure out what people need.
17+
Your full name, should you need to know it, is
18+
Splendid Speckled Mosscap. You communicate
19+
effectively, but you tend to answer with long
20+
flowery prose.
21+
"""
22+
23+
kernel = sk.Kernel()
24+
25+
service_id = "chat-gpt"
26+
chat_service = sk_oai.AzureChatCompletion(
27+
service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True)
28+
)
29+
kernel.add_service(chat_service)
30+
31+
req_settings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)
32+
req_settings.max_tokens = 2000
33+
req_settings.temperature = 0.7
34+
req_settings.top_p = 0.8
35+
req_settings.auto_invoke_kernel_functions = False
36+
37+
38+
chat_function = kernel.create_function_from_prompt(
39+
prompt="""{{system_message}}{% for item in chat_history %}{{ message(item) }}{% endfor %}""",
40+
function_name="chat",
41+
plugin_name="chat",
42+
template_format="jinja2",
43+
prompt_execution_settings=req_settings,
44+
)
45+
46+
chat_history = ChatHistory()
47+
chat_history.add_user_message("Hi there, who are you?")
48+
chat_history.add_assistant_message("I am Mosscap, a chat bot. I'm trying to figure out what people need.")
49+
50+
51+
async def chat() -> bool:
52+
try:
53+
user_input = input("User:> ")
54+
except KeyboardInterrupt:
55+
print("\n\nExiting chat...")
56+
return False
57+
except EOFError:
58+
print("\n\nExiting chat...")
59+
return False
60+
61+
if user_input == "exit":
62+
print("\n\nExiting chat...")
63+
return False
64+
chat_history.add_user_message(user_input)
65+
arguments = KernelArguments(system_message=system_message, chat_history=chat_history)
66+
67+
stream = True
68+
if stream:
69+
answer = kernel.invoke_stream(
70+
chat_function,
71+
arguments=arguments,
72+
)
73+
print("Mosscap:> ", end="")
74+
async for message in answer:
75+
print(str(message[0]), end="")
76+
print("\n")
77+
return True
78+
answer = await kernel.invoke(
79+
chat_function,
80+
arguments=arguments,
81+
)
82+
print(f"Mosscap:> {answer}")
83+
chat_history.add_assistant_message(str(answer))
84+
return True
85+
86+
87+
async def main() -> None:
88+
chatting = True
89+
while chatting:
90+
chatting = await chat()
91+
92+
93+
if __name__ == "__main__":
94+
asyncio.run(main())

‎python/semantic_kernel/exceptions/template_engine_exceptions.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ class HandlebarsTemplateRenderException(BlockRenderException):
8787
pass
8888

8989

90+
class Jinja2TemplateSyntaxError(BlockSyntaxError):
91+
pass
92+
93+
94+
class Jinja2TemplateRenderException(BlockRenderException):
95+
pass
96+
97+
9098
__all__ = [
9199
"BlockException",
92100
"BlockSyntaxError",
@@ -103,4 +111,6 @@ class HandlebarsTemplateRenderException(BlockRenderException):
103111
"TemplateRenderException",
104112
"HandlebarsTemplateSyntaxError",
105113
"HandlebarsTemplateRenderException",
114+
"Jinja2TemplateSyntaxError",
115+
"Jinja2TemplateRenderException",
106116
]

‎python/semantic_kernel/functions/kernel_function.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
from semantic_kernel.kernel_pydantic import KernelBaseModel
1313
from semantic_kernel.prompt_template.const import (
1414
HANDLEBARS_TEMPLATE_FORMAT_NAME,
15+
JINJA2_TEMPLATE_FORMAT_NAME,
1516
KERNEL_TEMPLATE_FORMAT_NAME,
1617
TEMPLATE_FORMAT_TYPES,
1718
)
1819
from semantic_kernel.prompt_template.handlebars_prompt_template import HandlebarsPromptTemplate
20+
from semantic_kernel.prompt_template.jinja2_prompt_template import Jinja2PromptTemplate
1921
from semantic_kernel.prompt_template.kernel_prompt_template import KernelPromptTemplate
2022

2123
if TYPE_CHECKING:
@@ -32,6 +34,7 @@
3234
TEMPLATE_FORMAT_MAP = {
3335
KERNEL_TEMPLATE_FORMAT_NAME: KernelPromptTemplate,
3436
HANDLEBARS_TEMPLATE_FORMAT_NAME: HandlebarsPromptTemplate,
37+
JINJA2_TEMPLATE_FORMAT_NAME: Jinja2PromptTemplate,
3538
}
3639

3740

‎python/semantic_kernel/kernel.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
from semantic_kernel.functions.kernel_plugin_collection import KernelPluginCollection
4141
from semantic_kernel.kernel_pydantic import KernelBaseModel
4242
from semantic_kernel.prompt_template.const import (
43-
HANDLEBARS_TEMPLATE_FORMAT_NAME,
4443
KERNEL_TEMPLATE_FORMAT_NAME,
4544
TEMPLATE_FORMAT_TYPES,
4645
)
@@ -376,7 +375,9 @@ async def invoke_prompt(
376375
prompt: str,
377376
arguments: Optional[KernelArguments] = None,
378377
template_format: Literal[
379-
KERNEL_TEMPLATE_FORMAT_NAME, HANDLEBARS_TEMPLATE_FORMAT_NAME
378+
"semantic-kernel",
379+
"handlebars",
380+
"jinja2",
380381
] = KERNEL_TEMPLATE_FORMAT_NAME,
381382
**kwargs: Any,
382383
) -> Optional[Union[FunctionResult, List[FunctionResult]]]:

‎python/semantic_kernel/prompt_template/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
from semantic_kernel.prompt_template.handlebars_prompt_template import HandlebarsPromptTemplate
44
from semantic_kernel.prompt_template.input_variable import InputVariable
5+
from semantic_kernel.prompt_template.jinja2_prompt_template import Jinja2PromptTemplate
56
from semantic_kernel.prompt_template.kernel_prompt_template import KernelPromptTemplate
67
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
78

89
__all__ = [
910
"KernelPromptTemplate",
1011
"HandlebarsPromptTemplate",
12+
"Jinja2PromptTemplate",
1113
"InputVariable",
1214
"PromptTemplateConfig",
1315
]

0 commit comments

Comments
 (0)