Was sind Filter?

Filter verbessern die Sicherheit, indem sie Kontrolle und Sichtbarkeit darüber bereitstellen, wie und wann Funktionen ausgeführt werden. Dies ist erforderlich, um verantwortungsvolle KI-Prinzipien in Ihre Arbeit zu integrieren, damit Sie sicher sind, dass Ihre Lösung für Unternehmen bereit ist.

Beispielsweise werden Filter verwendet, um Berechtigungen zu überprüfen, bevor ein Genehmigungsfluss beginnt. Der Filter wird ausgeführt, um die Berechtigungen der Person zu überprüfen, die eine Genehmigung senden möchte. Dies bedeutet, dass nur eine ausgewählte Gruppe von Personen den Prozess starten kann.

Ein gutes Beispiel für Filter finden Sie hier in unserem detaillierten Blogbeitrag "Semantischer Kernel" zu Filtern.   Semantischer Kernelfilter

Es gibt drei Arten von Filtern:

  • Funktionsaufruffilter – dieser Filter wird jedes Mal ausgeführt, wenn ein KernelFunction Filter aufgerufen wird. Sie ermöglicht Folgendes:

    • Zugriff auf Informationen über die ausgeführte Funktion und deren Argumente
    • Behandeln von Ausnahmen während der Funktionsausführung
    • Überschreiben des Funktionsergebnisses , entweder vor (z. B. für Zwischenspeicherungsszenarios) oder nach der Ausführung (z. B. für verantwortungsvolle KI-Szenarien)
    • Wiederholen der Funktion im Falle eines Fehlers (z. B. Wechseln zu einem alternativen KI-Modell)
  • Eingabeaufforderungs-Renderfilter – dieser Filter wird aktiviert, bevor das Rendering der Eingabeaufforderung erfolgt, wodurch Folgendes ermöglicht wird:

    • Anzeigen und Ändern der Aufforderung, die an die KI gesendet wird (z. B. für RAG- oder PII-Redaction)
    • Verhindern der Aufforderungsübermittlung an die KI durch Überschreiben des Funktionsergebnisses (z. B. für semantisches Zwischenspeichern)
  • Funktionsaufruffilter – dieser Filter wird jedes Mal ausgeführt, wenn ein KernelFunction Filter aufgerufen wird. Sie ermöglicht Folgendes:

    • Zugriff auf Informationen über die ausgeführte Funktion und deren Argumente
    • Behandeln von Ausnahmen während der Funktionsausführung
    • Überschreiben des Funktionsergebnisses , entweder vor (z. B. für Zwischenspeicherungsszenarios) oder nach der Ausführung (z. B. für verantwortungsvolle KI-Szenarien)
    • Wiederholen der Funktion im Falle eines Fehlers (z. B. Wechseln zu einem alternativen KI-Modell)
  • Prompt Render Filter – dieser Filter wird vor der Render-Operation der Eingabeaufforderung ausgelöst und ermöglicht:

    • Anzeigen und Ändern der Eingabeaufforderung, die an die KI gesendet wird
    • Verhindern der Aufforderungsübermittlung an die KI durch Überschreiben des Funktionsergebnisses (z. B. für semantisches Zwischenspeichern)
  • Funktionsaufruffilter – dieser Filter wird jedes Mal ausgeführt, wenn ein KernelFunction Filter aufgerufen wird. Sie ermöglicht Folgendes:

    • Zugriff auf Informationen über die ausgeführte Funktion und deren Argumente
    • Behandeln von Ausnahmen während der Funktionsausführung
    • Überschreiben des Funktionsergebnisses , entweder vor (z. B. für Zwischenspeicherungsszenarios) oder nach der Ausführung (z. B. für verantwortungsvolle KI-Szenarien)
    • Wiederholen der Funktion im Falle eines Fehlers
  • Prompt-Renderfilter – dieser Filter wird vor der Ausgabe der Eingabeaufforderung ausgelöst, wodurch Folgendes aktiviert wird:

    • Anzeigen und Ändern der Eingabeaufforderung, die an die KI gesendet wird
    • Verhindern der Aufforderungsübermittlung an die KI durch Außerkraftsetzung des Funktionsergebnisses
  • Auto Function Invocation Filter - ähnlich wie der Funktionsaufruffilter, wird dieser Filter innerhalb des Gültigkeitsbereichs automatic function callingausgeführt, bietet zusätzlichen Kontext, einschließlich Chatverlauf, eine Liste aller auszuführenden Funktionen und Iterationsindikatoren. Es ermöglicht auch das Beenden des Automatisch-Funktionsaufrufprozesses (z. B. wenn ein gewünschtes Ergebnis aus der zweiten von drei geplanten Funktionen abgerufen wird).

Jeder Filter enthält ein context Objekt, das alle relevanten Informationen zur Funktionsausführung oder zum Eingabeaufforderungsrendering enthält. Darüber hinaus verfügt jeder Filter über einen next Delegat/Rückruf, um den nächsten Filter in der Pipeline oder der Funktion selbst auszuführen, und bietet kontrolle über die Funktionsausführung (z. B. in Fällen böswilliger Eingabeaufforderungen oder Argumente). Mehrere Filter desselben Typs können registriert werden, jeweils mit eigener Verantwortung.

In einem Filter ist das Aufrufen der next Stellvertretung unerlässlich, um mit dem nächsten registrierten Filter oder dem ursprünglichen Vorgang fortzufahren (unabhängig davon, ob Funktionsaufruf oder Eingabeaufforderungsrendering). Ohne Aufruf nextwird der Vorgang nicht ausgeführt.

Um einen Filter zu verwenden, definieren Sie ihn zuerst, und fügen Sie ihn dem Objekt entweder über Abhängigkeitseinfügung oder die entsprechende Kernel Eigenschaft hinzuKernel. Bei verwendung der Abhängigkeitseinfügung ist die Reihenfolge der Filter nicht garantiert, sodass bei mehreren Filtern die Ausführungsreihenfolge unvorhersehbar sein kann.

Um einen Filter zu verwenden, können Sie entweder eine Funktion mit den erforderlichen Parametern definieren und mithilfe der Kernel Methode (Übergeben eines add_filter Werts oder seiner Zeichenfolgenäquivalent) für das FilterTypes Objekt registrieren oder den @kernel.filter Dekorateur verwenden, um den Filter in einem Schritt zu definieren und zu registrieren.

Funktionsaufruffilter

Dieser Filter wird jedes Mal ausgelöst, wenn eine Semantischer Kernel Funktion aufgerufen wird, unabhängig davon, ob es sich um eine Funktion handelt, die aus einer Eingabeaufforderung oder einer Methode erstellt wurde.

/// <summary>
/// Example of function invocation filter to perform logging before and after function invocation.
/// </summary>
public sealed class LoggingFilter(ILogger logger) : IFunctionInvocationFilter
{
    public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
    {
        logger.LogInformation("FunctionInvoking - {PluginName}.{FunctionName}", context.Function.PluginName, context.Function.Name);

        await next(context);

        logger.LogInformation("FunctionInvoked - {PluginName}.{FunctionName}", context.Function.PluginName, context.Function.Name);
    }
}

Hinzufügen eines Filters mit Abhängigkeitsinjektion:

IKernelBuilder builder = Kernel.CreateBuilder();

builder.Services.AddSingleton<IFunctionInvocationFilter, LoggingFilter>();

Kernel kernel = builder.Build();

Hinzufügen eines Filters mithilfe der Kernel Eigenschaft:

kernel.FunctionInvocationFilters.Add(new LoggingFilter(logger));

Code-Beispiele


import logging
from typing import Awaitable, Callable
from semantic_kernel.filters import FilterTypes, FunctionInvocationContext

logger = logging.getLogger(__name__)

async def logger_filter(context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]]) -> None:
    logger.info(f"FunctionInvoking - {context.function.plugin_name}.{context.function.name}")

    await next(context)

    logger.info(f"FunctionInvoked - {context.function.plugin_name}.{context.function.name}")

# Add filter to the kernel
kernel.add_filter(FilterTypes.FUNCTION_INVOCATION, logger_filter)

Sie können den @kernel.filter Dekorateur auch verwenden, um einen Filter direkt zu registrieren:


@kernel.filter(FilterTypes.FUNCTION_INVOCATION)
async def logger_filter(context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]]) -> None:
    logger.info(f"FunctionInvoking - {context.function.plugin_name}.{context.function.name}")

    await next(context)

    logger.info(f"FunctionInvoked - {context.function.plugin_name}.{context.function.name}")

Code-Beispiele

Weitere Informationen werden in Kürze verfügbar sein.

Prompt-Render-Filter

Dieser Filter wird nur während eines Eingabeaufforderungsrenderingvorgangs aufgerufen, z. B. wenn eine aus einer Eingabeaufforderung erstellte Funktion aufgerufen wird. Es wird nicht für Semantischer Kernel funktionen ausgelöst, die aus Methoden erstellt wurden.

/// <summary>
/// Example of prompt render filter which overrides rendered prompt before sending it to AI.
/// </summary>
public class SafePromptFilter : IPromptRenderFilter
{
    public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
    {
        // Example: get function information
        var functionName = context.Function.Name;

        await next(context);

        // Example: override rendered prompt before sending it to AI
        context.RenderedPrompt = "Safe prompt";
    }
}

Hinzufügen eines Filters mit Abhängigkeitsinjektion:

IKernelBuilder builder = Kernel.CreateBuilder();

builder.Services.AddSingleton<IPromptRenderFilter, SafePromptFilter>();

Kernel kernel = builder.Build();

Hinzufügen eines Filters mithilfe der Kernel Eigenschaft:

kernel.PromptRenderFilters.Add(new SafePromptFilter());

Code-Beispiele

from typing import Awaitable, Callable
from semantic_kernel.filters import FilterTypes, PromptRenderContext

async def safe_prompt_filter(
    context: PromptRenderContext,
    next: Callable[[PromptRenderContext], Awaitable[None]],
) -> None:
    # Example: get function information
    function_name = context.function.name

    await next(context)

    # Example: override the rendered prompt before sending it to the AI
    context.rendered_prompt = f"Safe prompt: {context.rendered_prompt or ''}"

# Register the filter on the kernel
kernel.add_filter(FilterTypes.PROMPT_RENDERING, safe_prompt_filter)

Sie können den @kernel.filter Dekorateur auch verwenden, um einen Filter direkt zu registrieren:

@kernel.filter(FilterTypes.PROMPT_RENDERING)
async def prompt_rendering_filter(context: PromptRenderContext, next):
    await next(context)
    context.rendered_prompt = f"You pretend to be Mosscap, but you are Papssom who is the opposite of Moscapp in every way {context.rendered_prompt or ''}"

Code-Beispiele

Weitere Informationen werden in Kürze verfügbar sein.

Automatischer Funktionsaufruf-Filter

Dieser Filter wird nur während eines automatischen Funktionsaufrufvorgangs aufgerufen. Er wird nicht ausgelöst, wenn eine Funktion außerhalb dieses Prozesses aufgerufen wird.

/// <summary>
/// Example of auto function invocation filter which terminates function calling process as soon as we have the desired result.
/// </summary>
public sealed class EarlyTerminationFilter : IAutoFunctionInvocationFilter
{
    public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
    {
        // Call the function first.
        await next(context);

        // Get a function result from context.
        var result = context.Result.GetValue<string>();

        // If the result meets the condition, terminate the process.
        // Otherwise, the function calling process will continue.
        if (result == "desired result")
        {
            context.Terminate = true;
        }
    }
}

Hinzufügen eines Filters mit Abhängigkeitsinjektion:

IKernelBuilder builder = Kernel.CreateBuilder();

builder.Services.AddSingleton<IAutoFunctionInvocationFilter, EarlyTerminationFilter>();

Kernel kernel = builder.Build();

Hinzufügen eines Filters mithilfe der Kernel Eigenschaft:

kernel.AutoFunctionInvocationFilters.Add(new EarlyTerminationFilter());

Code-Beispiele


from semantic_kernel.filters import FilterTypes, AutoFunctionInvocationContext

@kernel.filter(FilterTypes.AUTO_FUNCTION_INVOCATION)
async def auto_function_invocation_filter(context: AutoFunctionInvocationContext, next):
    await next(context)
    if context.function_result == "desired result":
        context.terminate = True

Wie bei den anderen Filtertypen können Sie den Filter auch mit kernel.add_filter:

kernel.add_filter(FilterTypes.AUTO_FUNCTION_INVOCATION, auto_function_invocation_filter)

Code-Beispiele

Weitere Informationen werden in Kürze verfügbar sein.

Streaming- und nicht-Streaming-Aufrufe

Funktionen in Semantischer Kernel können auf zwei Arten aufgerufen werden: Streaming und Nicht-Streaming. Im Streamingmodus gibt eine Funktion in der Regel zurück IAsyncEnumerable<T>, während sie im Nicht-Streaming-Modus zurückgegeben wird FunctionResult. Dieser Unterschied wirkt sich darauf aus, wie Ergebnisse im Filter überschrieben werden können: Im Streamingmodus muss der neue Funktionsergebniswert vom Typ IAsyncEnumerable<T>sein, während er im Nicht-Streaming-Modus einfach vom Typ Tsein kann. Um zu ermitteln, welcher Ergebnistyp zurückgegeben werden muss, ist das context.IsStreaming Flag im Filterkontextmodell verfügbar.

/// <summary>Filter that can be used for both streaming and non-streaming invocation modes at the same time.</summary>
public sealed class DualModeFilter : IFunctionInvocationFilter
{
    public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
    {
        // Call next filter in pipeline or actual function.
        await next(context);

        // Check which function invocation mode is used.
        if (context.IsStreaming)
        {
            // Return IAsyncEnumerable<string> result in case of streaming mode.
            var enumerable = context.Result.GetValue<IAsyncEnumerable<string>>();
            context.Result = new FunctionResult(context.Result, OverrideStreamingDataAsync(enumerable!));
        }
        else
        {
            // Return just a string result in case of non-streaming mode.
            var data = context.Result.GetValue<string>();
            context.Result = new FunctionResult(context.Result, OverrideNonStreamingData(data!));
        }
    }

    private async IAsyncEnumerable<string> OverrideStreamingDataAsync(IAsyncEnumerable<string> data)
    {
        await foreach (var item in data)
        {
            yield return $"{item} - updated from filter";
        }
    }

    private string OverrideNonStreamingData(string data)
    {
        return $"{data} - updated from filter";
    }
}

Verwenden von Filtern mit IChatCompletionService

In Fällen, in denen IChatCompletionService direkt anstelle von Kernel verwendet wird, werden Filter nur aufgerufen, wenn ein Kernel Objekt als Parameter an die Methoden des Chatabschlussdienstes übergeben wird, da Filter an die Kernel Instanz angebunden sind.

Kernel kernel = Kernel.CreateBuilder()
    .AddOpenAIChatCompletion("gpt-4", "api-key")
    .Build();

kernel.FunctionInvocationFilters.Add(new MyFilter());

IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

// Passing a Kernel here is required to trigger filters.
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);

Funktionen in Semantischer Kernel können auf zwei Arten aufgerufen werden: Streaming und Nicht-Streaming. Im Streamingmodus gibt eine Funktion in der Regel ein AsyncGenerator[T]-Objekt zurück, wobei T eine Art von Streaminginhaltstyp ist, während sie im Nicht-Streaming-Modus ein FunctionResult zurückgibt. Dieser Unterschied wirkt sich darauf aus, wie Ergebnisse im Filter überschrieben werden können: Im Streamingmodus muss der neue Funktionsergebniswert ebenfalls vom Typ AsyncGenerator[T]sein. Um zu bestimmen, welcher Ergebnistyp zurückgegeben werden muss, ist das context.is_streaming Flag in allen Filterkontextmodellen verfügbar.

Um also einen einfachen Loggerfilter für einen Aufruf einer Streamingfunktion zu erstellen, würden Sie etwas wie folgt verwenden:

@kernel.filter(FilterTypes.FUNCTION_INVOCATION)
async def streaming_exception_handling(
    context: FunctionInvocationContext,
    next: Callable[[FunctionInvocationContext], Awaitable[None]],
):
    await next(context)
    if not context.is_streaming:
        return

    async def override_stream(stream):
        try:
            async for partial in stream:
                yield partial
        except Exception as e:
            yield [
                StreamingChatMessageContent(role=AuthorRole.ASSISTANT, content=f"Exception caught: {e}", choice_index=0)
            ]

    stream = context.result.value
    context.result = FunctionResult(function=context.result.function, value=override_stream(stream))

Code-Beispiele

Weitere Informationen werden in Kürze verfügbar sein.

Bestellen

Bei Verwendung der Abhängigkeitsinjektion ist die Reihenfolge der Filter nicht garantiert. Wenn die Reihenfolge der Filter wichtig ist, empfiehlt es sich, Filter direkt dem Kernel-Objekt mithilfe geeigneter Eigenschaften hinzuzufügen. Mit diesem Ansatz können Filter zur Laufzeit hinzugefügt, entfernt oder neu angeordnet werden.

Filter werden in der Reihenfolge ausgeführt, in der sie dem Kernel-Objekt hinzugefügt werden – ganz gleich, ob durch add_filter oder über den @kernel.filter Dekorateur. Da sich die Ausführungsreihenfolge auf das Verhalten auswirken kann, ist es wichtig, die Filterreihenfolge sorgfältig zu verwalten.

Betrachten Sie folgendes Beispiel:

def func():
    print('function')


@kernel.filter(FilterTypes.FUNCTION_INVOCATION)
async def filter1(context: FunctionInvocationContext, next):
    print('before filter 1')
    await next(context)
    print('after filter 1')

@kernel.filter(FilterTypes.FUNCTION_INVOCATION)
async def filter2(context: FunctionInvocationContext, next):
    print('before filter 2')
    await next(context)
    print('after filter 2')

Beim Ausführen der Funktion lautet die Ausgabe wie folgt:

before filter 1
before filter 2
function
after filter 2
after filter 1

Weitere Beispiele