Software is hard
Software is hard

Intro to Semantic Kernel – Part Three

5 minutes read

In this installment, we delve into “Hooks.” From a programming standpoint, Hooks are essentially callbacks—functions with a predetermined signature, linked to a particular event. The event’s occurrence triggers these functions, earning them the moniker “callback” or “event handler.” Semantic Kernel recently introduced two such functions, IKernel.FunctionInvoking and IKernel.FunctionInvoked. Unfortunately, MSDN hasn’t documented these two event handlers likely due to Semantic Kernel’s beta status. However, the pace is quickening, and a 1.0.0 version is anticipated by year-end, barring misinformation. For now, here’s what I unearthed about them while spelunking through Semantic Kernel’s source code:

The Utility of Hooks

Hooks find applications in numerous scenarios like tracing and logging, or influencing the execution of Semantic Kernel functions—be it pausing & resuming them or altering input values dynamically. Their utility largely hinges on the case at hand. It’s crucial to note that Hooks are solely reactive to function executions carried out directly by the Kernel. They don’t play well with scenarios involving Planners. Initially, I envisioned leveraging Hooks to orchestrate the execution of Action Planners. Picture a Planner fetching an HTML page, followed by a Hook purging any embedded JavaScript before proceeding. However, my Hooks remained dormant. After some tinkering, the epiphany hit—thorough reading is a time-saver. It makes absolute sense that Hooks should steer clear during Planner executions, given the autonomous, multi-step program assembly nature of Planners. Injecting Hooks into such a dynamic could unleash a pandemonium of issues. Imagine a Sequential Planner bamboozled by a mischievous Hook feeding it bogus data unbeknownst to it. The potential Turing Completeness could morph into a lethal quagmire. While I’m unsure about the Turing completeness of Planners, interposing Hooks seems like a perilous venture. But this journey of exploration continues, and for now, let’s harness Hooks in their intended capacity—as callbacks triggered during Kernel-driven function executions.

Setting up Functions and Hooks

Firstly, we need a function for the Kernel to execute. It can be either semantic or native. In the ensuing examples, we’ll be engaging with both. Let’s kickstart with a semantic function. Below is the “config.json” definition done in C# (and yes, old-school coders, semantic functions aren’t confined to skprompt.txt).

Here, we’ve crafted a prompt template config with the customary settings. It sets the stage for a function that fetches information about a specified individual, whose name is provided as the sole input parameter. Following this, we’ll delve into the semantic function definition, also articulated in C#.

Next on the agenda is defining the two Hooks.

In this scenario, we aim for the FunctionInvoking-Hook to announce the upcoming function to be executed. The FunctionInvoked-Hook takes it a notch higher—it snags the result from LLM, and shuttles it to a helper function. This function jazzes up the plain text by sprinkling # and ## onto paragraphs from the original document, morphing it into Markdown. While simplistic, this example nicely illustrates how we can buff up LLM results a tad.

Hooking it up with Hooks

The previous illustration showcased how we can tweak the data emerging from LLM. But what about tweaking elements before our prompt graces LLM? Hooks have got our back here too. Picture a tried-and-true function you’ve been utilizing in diverse contexts. Suddenly, an unexpected twist necessitates an alteration, perhaps in the name of one of its arguments. Although a straightforward fix, the ripple effect could be monumental—legacy code might hit a snag. Now what? Hooks could be the lifeline: no changes needed. Just tweak the SK Context Variables before they rendezvous with the concerned function. Below is a function, previously featured, as an example.

This function is a resident of the HttpPlugin, nestled here. Its sole companion is the “url” argument, tasked with fetching the resource residing at the specified URL. Simple enough, right? Now, let’s transition to our other Hooks demonstration code.

We’ve ushered in the HttpPlugin, set up our Hooks, and penciled in the URL for download. Smooth sailing so far? Not exactly. A hiccup surfaces as kernel.RunAsync stumbles. The snapshot below captures the ordeal.

Our native function, ExecuteGet, scoured SKContext.Variables for the “url” but came up empty-handed. Yet, a debug run reveals an unsolicited addition. Clearly, our hands are clean.

Instead of “url”, the Context is harboring the variable “INPUT”, a default setting. So, how do we steer ExecuteGet to its “url” without tweaking anything else? The answer, unsurprisingly, is Hooks!

With this adjustment, the execution sails smoothly without a hitch, thanks to the presence of the anticipated variable.

This is merely one route to ensuring the right variables rendezvous with the intended functions. A peek at the signature of the Kernel’s RunAsync method unveils alternative avenues for configuring a function and its execution environment.

I trust this introduction has furnished you with a solid foundation on Hooks, and how to wield them within SK.

Immerse yourself in the wonders of Semantic Kernel and have a blast!

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.