Co to są filtry?

Filtry zwiększają bezpieczeństwo, zapewniając kontrolę i widoczność sposobu i czasu działania funkcji. Jest to konieczne, aby wprowadzić zasady odpowiedzialnej sztucznej inteligencji do pracy, aby mieć pewność, że twoje rozwiązanie jest gotowe do użycia w przedsiębiorstwie.

Na przykład filtry są używane do sprawdzania poprawności uprawnień przed rozpoczęciem przepływu zatwierdzania. Filtr jest uruchamiany w celu sprawdzenia uprawnień osoby, która chce przesłać zatwierdzenie. Oznacza to, że tylko wybrana grupa osób będzie mogła rozpocząć proces.

Dobry przykład filtrów znajduje się tutaj w naszym szczegółowym wpisie na blogu Semantic Kernel w temacie Filtry.   Filtry jądra semantycznego

Istnieją trzy typy filtrów:

  • Filtr wywołania funkcji — ten filtr jest wykonywany za każdym razem, gdy KernelFunction element jest wywoływany. Pozwala na:

    • Dostęp do informacji o wykonywanej funkcji i jej argumentach
    • Obsługa wyjątków podczas wykonywania funkcji
    • Zastępowanie wyniku funkcji przed wykonaniem (na przykład w przypadku scenariusza buforowania) lub po wykonaniu (na przykład w przypadku scenariuszy odpowiedzialnej sztucznej inteligencji)
    • Ponawianie próby funkcji w przypadku awarii (np. przełączenie do alternatywnego modelu sztucznej inteligencji)
  • Filtr generowania podpowiedzi — ten filtr jest wyzwalany przed operacją renderowania podpowiedzi, umożliwiając:

    • Wyświetlanie i modyfikowanie monitu, który zostanie wysłany do sztucznej inteligencji (np. w przypadku RAG lub redakcji PII)
    • Zapobieganie przesłaniu monitu do sztucznej inteligencji przez zastąpienie wyniku funkcji (np. w przypadku buforowania semantycznego)
  • Filtr wywołania funkcji — ten filtr jest wykonywany za każdym razem, gdy KernelFunction element jest wywoływany. Pozwala na:

    • Dostęp do informacji o wykonywanej funkcji i jej argumentach
    • Obsługa wyjątków podczas wykonywania funkcji
    • Zastępowanie wyniku funkcji przed wykonaniem (na przykład w przypadku scenariusza buforowania) lub po wykonaniu (na przykład w przypadku scenariuszy odpowiedzialnej sztucznej inteligencji)
    • Ponawianie próby funkcji w przypadku awarii (np. przełączenie do alternatywnego modelu sztucznej inteligencji)
  • Filtr generowania podpowiedzi — ten filtr jest wyzwalany przed operacją renderowania podpowiedzi, umożliwiając:

    • Wyświetlanie i modyfikowanie monitu, który zostanie wysłany do sztucznej inteligencji
    • Zapobieganie przesłaniu monitu do sztucznej inteligencji przez zastąpienie wyniku funkcji (np. w przypadku buforowania semantycznego)
  • Filtr wywołania funkcji — ten filtr jest wykonywany za każdym razem, gdy KernelFunction element jest wywoływany. Pozwala na:

    • Dostęp do informacji o wykonywanej funkcji i jej argumentach
    • Obsługa wyjątków podczas wykonywania funkcji
    • Zastępowanie wyniku funkcji przed wykonaniem (na przykład w przypadku scenariusza buforowania) lub po wykonaniu (na przykład w przypadku scenariuszy odpowiedzialnej sztucznej inteligencji)
    • Ponawianie próby funkcji w przypadku awarii
  • Filtr generowania podpowiedzi — ten filtr jest wyzwalany przed operacją renderowania podpowiedzi, umożliwiając:

    • Wyświetlanie i modyfikowanie monitu, który zostanie wysłany do sztucznej inteligencji
    • Zapobieganie przesłaniu monitu do sztucznej inteligencji przez zastąpienie wyniku funkcji
  • Filtr wywołania funkcji automatycznej — podobnie jak filtr wywołania funkcji, ten filtr działa w zakresie automatic function calling, zapewniając dodatkowy kontekst, w tym historię czatów, listę wszystkich funkcji do wykonania i liczniki iteracji. Umożliwia również zakończenie procesu automatycznego wywoływania funkcji (np. w przypadku uzyskania żądanego wyniku z drugiej z trzech planowanych funkcji).

Każdy filtr zawiera context obiekt zawierający wszystkie istotne informacje o wykonaniu funkcji lub renderowaniu monitu. Ponadto każdy filtr ma next delegata lub funkcję zwrotną do wykonania następnego filtru w ramach przetwarzania lub samej funkcji, oferując kontrolę nad wykonywaniem funkcji (np. w przypadkach złośliwych pytań lub argumentów). Można zarejestrować wiele filtrów tego samego typu, z których każdy ponosi własną odpowiedzialność.

W filtrze wywoływanie delegata next jest niezbędne, aby przejść do następnego zarejestrowanego filtru lub oryginalnej operacji (czy wywołania funkcji, czy renderowania monitu). Bez wywoływania next operacja nie zostanie wykonana.

Aby użyć filtru, najpierw zdefiniuj go, a następnie dodaj go do Kernel obiektu za pomocą iniekcji zależności lub odpowiedniej Kernel właściwości. W przypadku korzystania z wstrzykiwania zależności kolejność filtrów nie jest gwarantowana, więc w przypadku wielu filtrów kolejność wykonywania może być nieprzewidywalna.

Aby użyć filtru, można zdefiniować funkcję z wymaganymi parametrami i zarejestrować ją w Kernel obiekcie przy użyciu add_filter metody (przekazując FilterTypes wartość lub jej odpowiednik ciągu) albo użyć @kernel.filter dekoratora do zdefiniowania i zarejestrowania filtru w jednym kroku.

Filtr wywołania funkcji

Ten filtr jest wyzwalany za każdym razem, gdy wywoływana jest funkcja Semantic Kernel, niezależnie od tego, czy jest to funkcja utworzona na podstawie monitu, czy metody.

/// <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);
    }
}

Dodaj filtr przy użyciu wstrzykiwania zależności:

IKernelBuilder builder = Kernel.CreateBuilder();

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

Kernel kernel = builder.Build();

Dodaj filtr przy użyciu Kernel właściwości:

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

Przykłady kodu


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)

Możesz również użyć dekoratora @kernel.filter do zarejestrowania filtru bezpośrednio:


@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}")

Przykłady kodu

Więcej informacji wkrótce.

Szybki filtr renderowania

Ten filtr jest wywoływany tylko podczas operacji renderowania monitu, na przykład po wywołaniu funkcji utworzonej na podstawie monitu. Nie zostanie ona wyzwolona dla funkcji Semantic Kernel utworzonych na podstawie metod.

/// <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";
    }
}

Dodaj filtr przy użyciu wstrzykiwania zależności:

IKernelBuilder builder = Kernel.CreateBuilder();

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

Kernel kernel = builder.Build();

Dodaj filtr przy użyciu Kernel właściwości:

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

Przykłady kodu

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)

Możesz również użyć dekoratora @kernel.filter do zarejestrowania filtru bezpośrednio:

@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 ''}"

Przykłady kodu

Więcej informacji wkrótce.

Filtr wywołania funkcji automatycznej

Ten filtr jest wywoływany tylko podczas automatycznego procesu wywoływania funkcji. Nie zostanie ona wyzwolona, gdy funkcja zostanie wywołana poza tym procesem.

/// <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;
        }
    }
}

Dodaj filtr przy użyciu wstrzykiwania zależności:

IKernelBuilder builder = Kernel.CreateBuilder();

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

Kernel kernel = builder.Build();

Dodaj filtr przy użyciu Kernel właściwości:

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

Przykłady kodu


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

Podobnie jak w przypadku innych typów filtrów, można również zarejestrować filtr przy użyciu polecenia kernel.add_filter:

kernel.add_filter(FilterTypes.AUTO_FUNCTION_INVOCATION, auto_function_invocation_filter)

Przykłady kodu

Więcej informacji wkrótce.

Inicjowanie przesyłania strumieniowego i bez przesyłania strumieniowego

Funkcje w Semantic Kernel mogą być wywoływane na dwa sposoby: strumieniowo i bezstrumieniowo. W trybie przesyłania strumieniowego funkcja zwykle zwraca IAsyncEnumerable<T>, podczas gdy w trybie bez przesyłania strumieniowego zwraca FunctionResult. To rozróżnienie wpływa na sposób zastępowania wyników w filtrze: w trybie przesyłania strumieniowego nowa wartość wyniku funkcji musi być typu IAsyncEnumerable<T>, natomiast w trybie nieprzesyłania strumieniowego może być po prostu typu T. Aby określić, który typ wyniku należy zwrócić, flaga context.IsStreaming jest dostępna w modelu kontekstu filtru.

/// <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";
    }
}

Używanie filtrów z IChatCompletionService

W przypadkach, gdy IChatCompletionService jest używany bezpośrednio zamiast Kernel, filtry będą wywoływane tylko wtedy, gdy obiekt Kernel jest przekazywany jako parametr do metod usługi kompletacji czatu, ponieważ filtry są dołączone do instancji Kernel.

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);

Funkcje w Semantic Kernel mogą być wywoływane na dwa sposoby: strumieniowo i bezstrumieniowo. W trybie przesyłania strumieniowego funkcja zwykle zwraca AsyncGenerator[T] obiekt, w którym T jest rodzajem zawartości przesyłanej strumieniowo, podczas gdy w trybie niestreamingowym zwraca wartość FunctionResult. To rozróżnienie wpływa na sposób zastępowania wyników w filtrze: w trybie przesyłania strumieniowego nowa wartość wyniku funkcji musi być również typu AsyncGenerator[T]. Aby określić, który typ wyniku należy zwrócić, flaga context.is_streaming jest dostępna we wszystkich modelach kontekstu filtru.

Aby utworzyć prosty filtr rejestratora dla wywołania funkcji przesyłania strumieniowego, należy użyć czegoś takiego:

@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))

Przykłady kodu

Więcej informacji wkrótce.

Zamawianie

W przypadku korzystania z wstrzykiwania zależności kolejność filtrów nie jest gwarantowana. Jeśli kolejność filtrów jest ważna, zaleca się dodanie filtrów bezpośrednio do Kernel obiektu przy użyciu odpowiednich właściwości. Takie podejście umożliwia dodawanie, usuwanie lub zmienianie kolejności filtrów w czasie wykonywania.

Filtry są wykonywane w kolejności, w której są dodawane do obiektu jądra — za pośrednictwem add_filter lub dekoratora @kernel.filter . Ponieważ kolejność wykonywania może mieć wpływ na zachowanie, ważne jest, aby uważnie zarządzać kolejnością filtrów.

Rozważmy następujący przykład:

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')

Podczas wykonywania funkcji dane wyjściowe będą następujące:

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

Więcej przykładów