The purpose of this article is to help you quickly grasp the key concepts in Semantic Kernel and get started quickly.
In Semantic Kernel Java, the builder pattern is extensively used. If you are not familiar with the builder pattern, I recommend you check out: Builder Design Pattern
All the code examples below are from samples/semantickernel-concepts/semantickernel-syntax-examples
.
ChatCompletionServicechatCompletionService = ChatCompletionService.builder() .withOpenAIAsyncClient(client) .withModelId("gpt-3.5-turbo-0613") .withServiceId("fridayChatGeneration") .build();
Retrieve the AI Service from the Kernel
ChatCompletionServiceservice = kernel.getService(ChatCompletionService.class);
Directly call
service.getChatMessageContentsAsync
to get the LLM responseChatCompletionServiceservice = kernel.getService(ChatCompletionService.class); varchatHistory = newChatHistory(systemMessage); chatHistory.addUserMessage(userMessage); varanswer = service.getChatMessageContentsAsync(chatHistory, kernel, null).block();
The KernelBuilder
is a builder used to create and configure a new Kernel
with necessary services and plugins.
ChatCompletionServicechatCompletionService = ChatCompletionService.builder() .withOpenAIAsyncClient(client) .withModelId("gpt-3.5-turbo-0613") .withServiceId("fridayChatGeneration") .build(); returnKernel.builder() .withAIService(ChatCompletionService.class, chatCompletionService);
A Kernel
is created using the KernelBuilder
, where various services and plugins are configured via withXXX()
.
Create a Kernel using KernelBuilder
and configure the necessary parameters
Kernelkernel = Kernel.builder() .withPlugin(myPlugin) .withAIService(openAiChatService) .withServiceSelector() .build();
Define a custom class
Construct using
KernelPluginFactory
publicstaticclassTime { @DefineKernelFunction(name = "date") publicStringdate() { System.out.println("date is called"); Datenow = newDate(); SimpleDateFormatdateFormat = newSimpleDateFormat("yyyy-MM-dd"); returndateFormat.format(now); } @DefineKernelFunction(name = "time") publicStringtime() { System.out.println("time is called"); Datenow = newDate(); SimpleDateFormattimeFormat = newSimpleDateFormat("HH:mm:ss"); returntimeFormat.format(now); } } KernelPlugintime = KernelPluginFactory.createFromObject(newTime(), "time");
A native function in Semantic Kernel performs precise tasks like data retrieval, time checks, and complex math, which large language models (LLMs) may make mistake. Native functions are written in code and ensure accuracy. In contrast, LLMs offer flexibility, generality, and creativity, excelling in generating and predicting text. Combining both leverages their respective strengths for optimal performance. For more details, refer to Microsoft Documentation on Kernel Functions.
Here’s an example of how to define a native kernel function:
publicclassTextPlugin { @DefineKernelFunction(description = "Change all string chars to uppercase.", name = "Uppercase") publicStringuppercase(@KernelFunctionParameter(description = "Text to uppercase", name = "input") Stringtext) { returntext.toUpperCase(Locale.ROOT); } }
To create a inline KernelFunction from a prompt, you can use either of the following methods, which are equivalent:
KernelFunctionFromPrompt.builder().withTemplate(promptTemplate).build();
KernelFunction.createFromPrompt(message).build();
StringpromptTemplate = """ Generate a creative reason or excuse for the given event. Be creative and be funny. Let your imagination run wild. Event: I am running late. Excuse: I was being held ransom by giraffe gangsters. Event: I haven't been to the gym for a year Excuse: I've been too busy training my pet dragon. Event: {{$input}}""".stripIndent(); varexcuseFunction = KernelFunctionFromPrompt.builder() .withTemplate(promptTemplate) .withDefaultExecutionSettings( PromptExecutionSettings.builder() .withTemperature(0.4) .withTopP(1) .withMaxTokens(500) .withUser("bx-h") .build() ) .withName("ExcuseGeneratorFunction") .build();
varmessage = "Translate this date " + date + " to French format"; varfixedFunction = KernelFunction .createFromPrompt(message) .withDefaultExecutionSettings( PromptExecutionSettings.builder() .withMaxTokens(100) .build()) .withTemplateFormat(PromptTemplateConfig.SEMANTIC_KERNEL_TEMPLATE_FORMAT) .withName("translator") .build();
The SEMANTIC_KERNEL_TEMPLATE_FORMAT
corresponds to the 'semantic-kernel' rendering engine, which uses the syntax {{$variable}}
for variables.
Another rendering engine is 'handlebars', which uses the syntax {{variable}}
. Here's an example of how to use both:
runPrompt(kernel, "semantic-kernel", "Hello AI, my name is {{$name}}. What is the origin of my name?", templateFactory); runPrompt(kernel, "handlebars", "Hello AI, my name is {{name}}. What is the origin of my name?", templateFactory);
The runPrompt
method is defined as follows:
publicstaticvoidrunPrompt(Kernelkernel, StringtemplateFormat, Stringprompt, PromptTemplateFactorytemplateFactory) { varfunction = newKernelFunctionFromPrompt.Builder<>() .withTemplate(prompt) .withTemplateFormat(templateFormat) .withPromptTemplateFactory(templateFactory) .build(); vararguments = KernelFunctionArguments.builder() .withVariable("name", "Bob") .build(); varresult = kernel.invokeAsync(function).withArguments(arguments).block(); System.out.println(result.getResult()); }
For more information, please refer to the following resources:
- Microsoft Documentation on Prompt Template Syntax
- Microsoft Devblogs on Using Handlebars Planner in Semantic Kernel
Define a function from a configuration file (json)
varprompt = "Hello AI, what can you do for me?"; StringconfigPayload = """ { "schema": 1, "name": "HelloAI", "description": "Say hello to an AI", "type": "completion", "completion": { "max_tokens": 256, "temperature": 0.5, "top_p": 0.0, "presence_penalty": 0.0, "frequency_penalty": 0.0 } }""".stripIndent(); PromptTemplateConfigpromptConfig = PromptTemplateConfig .parseFromJson(configPayload) .copy() .withTemplate(prompt) .build(); varfunc = KernelFunction .createFromPrompt(promptConfig) .build();
KernelFunctionArguments.builder().withVariable("input", "Jupiter").build();
This can also be done as:
KernelFunctionArguments.builder().withInput("Jupiter").build();
- Direct call:
TextPlugintext = newTextPlugin(); returntext.uppercase("ciao!");
- Invoke via
Kernel.invokeAsync(KernelFunction)
Kernelkernel = Kernel.builder().build(); KernelPluginkernelPlugin = KernelPluginFactory.createFromObject(newStaticTextPlugin(), "text"); KernelFunctionArgumentsarguments = KernelFunctionArguments.builder() .withVariable("input", "Today is: ") .withVariable("day", "Monday") .build(); returnkernel.invokeAsync(kernelPlugin.get("AppendDay")) .withArguments(arguments) .block() .getResult();
OR:
varresult = kernel .invokeAsync(excuseFunction) .withArguments( KernelFunctionArguments.builder() .withInput("I missed the F1 final race") .build() ) .block();
The purpose of a prompt template is to:
- Render the prompt
- Be passed as a parameter to
createFromPrompt()
to constructKernelFunction
Define the prompt template
Create using
KernelPromptTemplateFactory()
StringfunctionDefinition = """ Today is: {{time.date}} Current time is: {{time.time}} Answer the following questions using JSON syntax, including the data used. Is it morning, afternoon, evening, or night (morning/afternoon/evening/night)? Is it weekend time (weekend/not weekend)?"""; PromptTemplatepromptTemplate = newKernelPromptTemplateFactory().tryCreate( PromptTemplateConfig .builder() .withTemplate(functionDefinition) .build() );
StringsystemPromptTemplate = "..."; PromptTemplatepromptTemplate = PromptTemplateFactory.build( PromptTemplateConfig .builder() .withTemplate(systemPromptTemplate) .build() );
varrenderedPrompt = promptTemplate.renderAsync(kernel, KernelFunctionArguments, InvocationContext).block(); System.out.println(renderedPrompt);
Hooks are functions triggered in specific situations attached to the kernel.
Global Registration: If added to
kernel.getGlobalKernelHooks()
, it is globally effectivekernel.getGlobalKernelHooks().addHook("hookName", KernelHook);
Single Call Registration: If passed as a parameter in
invokeAsync
, it is effective for that call onlyKernelHookskernelHooks = newKernelHooks(); kernelHooks.addPreChatCompletionHook(...); varresult = kernel.invokeAsync(writerFunction) .withArguments(KernelFunctionArguments.builder().build()) .addKernelHooks(kernelHooks) .block();
Triggered before function call.
FunctionInvokingHookpreHook = event -> { System.out.println(event.getFunction().getName() + " : Pre Execution Handler - Triggered"); returnevent; }; kernel.getGlobalKernelHooks().addHook("", preHook);
Triggered after function call
FunctionInvokedHookhook = event -> { Stringresult = (String) event.getResult().getResult(); System.out.println(event.getFunction().getName() + " : Modified result via FunctionInvokedHook: " + result); result = result.replaceAll("[aeiouAEIOU0-9]", "*"); returnnewFunctionInvokedEvent( event.getFunction(), event.getArguments(), newFunctionResult<>(ContextVariable.of(result), event.getResult().getMetadata(), result) ); }; kernel.getGlobalKernelHooks().addHook(hook);
PromptRenderingHookmyRenderingHandler = event -> { System.out.println(event.getFunction().getName() + " : Triggered PromptRenderingHook"); event.getArguments().put("style", ContextVariable.of("Seinfeld")); returnevent; };
PromptRenderedHookmyRenderedHandler = event -> { System.out.println(event.getFunction().getName() + " : Triggered PromptRenderedHook"); Stringprompt = event.getPrompt() + "\nUSE SHORT, CLEAR, COMPLETE SENTENCES."; returnnewPromptRenderedEvent(event.getFunction(), event.getArguments(), prompt); };
PromptRenderingHook
andPromptRenderedHook
are triggered only at the start of a conversation. They won't trigger during multiple tool calls. To trigger at every LLM interaction, useChatCompletionsHook
Add a pre-chat completion hook to add instructions before ChatCompletion.
kernel.getGlobalKernelHooks().addPreChatCompletionHook(event -> { ChatCompletionsOptionsoptions = event.getOptions(); List<ChatRequestMessage> messages = options.getMessages(); messages = newArrayList<>(messages); messages.add(newChatRequestSystemMessage("Use upper case text when responding to the prompt.")); System.out.println("------- Triggered before ChatCompletion -------"); System.out.println("---- Added: Use upper case text when responding to the prompt. ----"); for (ChatRequestMessagemsg : messages) { System.out.println(msg); } returnnewPreChatCompletionEvent( PreChatCompletionHook.cloneOptionsWithMessages(options, messages) ); });
Add a post-chat completion hook to adjust the output format
kernel.getGlobalKernelHooks().addPostChatCompletionHook(event -> { System.out.println("------- Triggered after ChatCompletion -------"); System.out.println("--- Output ChatCompletion and id ----"); System.out.println("Chat completion"); System.out.println("Id: " + event.getChatCompletions().getId()); returnevent; });