In my last post we explored how we can build a very simple agentic application using CrewAI. Personally, I like to build these hello world applications to understand better how the frameworks function. In this post we will go through the same procedure, but this time we are going to use Java and Microsoft’s Semantic Kernel framework.
Setting up the project
Not sure anyone still uses Java without any proper build tool such as Maven or Gradle. Therefore the very first thing is to setup a new project. As I am an IntelliJ user, that looks something like below
Which will lead to a new empty project, ready to get started!
So let us add the libraries according to the documentation. This is already the first pitfall here as they claim it to be as easy as that. But you will have to figure out the version to use yourself. I did that via https://mvnrepository.com/artifact/com.microsoft.semantic-kernel/semantickernel-api. I think they should add a hint here, it is just an additional annoyance if you want to get started swiftly.
Building a simple agentic example
The example at hand is very simple:
- There is one function (or tool) that we want to give the kernel access to, which is a simple string reversal function
- The idea is to be able to send a prompt such as: “What is the reverse of Miami?” and then have the application use that tool and provide the respective answer
The example code from the official documentation is a bit hard to get started, in my view. I would recommend making that simpler. There are quite some concepts coming together, just to build a simple application.
Essentially we do this:
- create a connection to our OpenAI endpoint
- Create our plugin (not sure when this changed, but I had in mind these were skills in the past, anyway, these seem to be the functions or tools we can provide to the kernel)
- We create a chat completion service, because
- Now we can create our kernel and combine all of the above
- Next up is an invocation context, because
- And finally we can invoke our little hello world application and be happy with the results!
The main code
package org.example;
import com.azure.ai.openai.OpenAIAsyncClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.microsoft.semantickernel.Kernel;
import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion;
import com.microsoft.semantickernel.orchestration.InvocationContext;
import com.microsoft.semantickernel.orchestration.InvocationReturnMode;
import com.microsoft.semantickernel.orchestration.ToolCallBehavior;
import com.microsoft.semantickernel.plugin.KernelPlugin;
import com.microsoft.semantickernel.plugin.KernelPluginFactory;
import com.microsoft.semantickernel.services.chatcompletion.AuthorRole;
import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService;
import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent;
import java.util.List;
public class Main {
private static final String AZURE_CLIENT_KEY = "xxx";
private static final String CLIENT_ENDPOINT = "https://xxx.openai.azure.com/";
public static void main(String[] args) {
OpenAIAsyncClient client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(AZURE_CLIENT_KEY))
.endpoint(CLIENT_ENDPOINT)
.buildAsyncClient();
KernelPlugin reversePlugin = KernelPluginFactory.createFromObject(new ReversePlugin(),
"ReversePlugin");
ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder()
.withModelId("gpt-4o-mini")
.withOpenAIAsyncClient(client)
.build();
Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.withPlugin(reversePlugin)
.build();
InvocationContext invocationContext = new InvocationContext.Builder()
.withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY)
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
.build();
String userInput = "Please reverse Miami.";
List<ChatMessageContent<?>> results = chatCompletionService
.getChatMessageContentsAsync(userInput, kernel, invocationContext)
.block();
for (ChatMessageContent<?> result : results) {
if (result.getAuthorRole() == AuthorRole.ASSISTANT && result.getContent() != null) {
System.out.println("Assistant > " + result);
}
}
}
}
The plugin (function, tool)
package org.example;
import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction;
public class ReversePlugin {
@DefineKernelFunction(name = "reverse_text", description = "Reverses the input text.")
public String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}
The output
Putting all these together, and we got our hello world application do its job!
Where is the code?
I usually don’t create public repositories for these examples, but you can find the hopefully simple getting started project here: https://github.com/christianmenz/hello-world-semantic-kernel-java
Conclusion
Semantic Kernel is maybe not the easiest framework to get started, as a lot of things need to be wired up. I wonder whether this could be simplified by just providing some meaningful defaults.
I do like the flexibility the framework offers and having something that works in the Java world is actually quite important as well. Although the post was not meant to be a review of Semantic Kernel, I still would like to see the entry barriers to be lowered a bit by providing simpler examples, more defaults and a clearer documentation.
Being a long time Java developer, I do find the integration allowing me to use and integrate it the way I need. I might replace my custom built approach with it for a productive service that I’m running and see how it works beyond a hello world setup.
Of course, this is only a simple example and there is much more to discover.