Funkcje promptu oparte na plikach w Semantic Kernel

W poprzednich artykułach omówiliśmy podstawowe aspekty korzystania z Semantic Kernel oraz sposób tworzenia i uruchamiania promptów inline. Dziś skupimy się na bardziej zaawansowanym podejściu — funkcjach promptów opartych na plikach. Dzięki temu możesz przenieść swoje prompty do dedykowanych plików i używać ich wielokrotnie, co ułatwia zarządzanie i poprawia czytelność projektu. W tym artykule dowiesz się, jak w praktyce wykorzystać takie podejście i dlaczego warto to zrobić.

Zalety funkcji promptów opartych na plikach

Stosowanie funkcji promptów opartych na plikach niesie ze sobą liczne korzyści:

  • Modularność i Przejrzystość: Przechowywanie promptów w osobnych plikach pozwala na oddzielenie logiki od danych, co zwiększa przejrzystość i organizację kodu.
  • Łatwość Aktualizacji: Aktualizowanie promptów w jednym centralnym miejscu sprawia, że zmiany są automatycznie stosowane tam, gdzie prompt jest używany, eliminując potrzebę modyfikacji wielu miejsc w kodzie.
  • Re-używalność: Pliki z promptami mogą być wykorzystywane w różnych projektach, co zmniejsza powielanie kodu, a tym samym ryzyko błędów i zapewnia większą spójność.
  • Lepsza Współpraca: Dzięki przechowywaniu promptów w plikach, zespół może łatwo współdzielić je w repozytorium, zapewniając jednolite standardy i ułatwiając pracę zespołową.

Elementy Składowe funkcji promptów opartych na plikach

Przechowywanie funkcji promptów w systemie plików opiera się na stworzeniu dwóch kluczowych plików:

  • skprompt.txt: To zwykły plik tekstowy, w którym definiujemy treść promptu, czyli to, co model ma zrobić. Możemy w nim zawrzeć zarówno konkretne instrukcje, jak i zmienne.
  • config.json: Plik konfiguracyjny, który określa parametry modelu oraz definiuje zmienne używane w promptach, co czyni naszą funkcję bardziej elastyczną.

W pliku skprompt.txt zapisujemy definicję promptu, która może także zawierać dynamiczne zmienne. Dzięki temu możemy łatwo dostosować działanie do konkretnych potrzeb.

Przykładowo, możemy stworzyć prompt, który instruuje model, aby przetłumaczył tekst z jednego języka na inny:

Translate the following text from {{$source_language}} to {{$target_language}}: "{{$text_to_translate}}"

W powyższym przykładzie użyliśmy zmiennych takich jak {{$source_language}}, {{$target_language}}, oraz {{$text_to_translate}}. Są one dynamiczne i mogą być zastępowane odpowiednimi wartościami podczas wywoływania funkcji, co umożliwia szerokie zastosowanie tego samego promptu w różnych kontekstach, bez konieczności każdorazowej jego edycji.

Plik config.json odgrywa kluczową rolę w określeniu konfiguracji działania modelu. Zawiera parametry, takie jak maksymalna liczba tokenów czy temperatura, które sterują sposobem generowania odpowiedzi przez model. Ponadto, w tym pliku definiujemy zmienne używane w pliku skprompt.txt, zapewniając ich zgodność i dynamiczne zastosowanie.

Poniżej znajduje się przykładowy plik config.json:

{
  "schema": 1,
  "description": "Translate text.",
  "default_services": [
    "gpt-4o-mini"
  ],
  "execution_settings": {
    "default": {
      "max_tokens": 1000,
      "temperature": 0.5
    }
  },
  "input_variables": [
    {
      "name": "$source_language",
      "description": "Language from which the text will be translated.",
      "required": true
    },
    {
      "name": "$target_language",
      "description": "Language into which the text will be translated.",
      "required": true
    },
    {
      "name": "$text_to_translate",
      "description": "The text that needs to be translated.",
      "required": true
    }
  ]
}

W powyższym przykładzie, execution_settings określa ustawienia działania, takie jak liczba tokenów (max_tokens) oraz temperatura (temperature), które wpływają na sposób generowania odpowiedzi — im wyższa temperatura, tym bardziej kreatywne będą wyniki. W sekcji input_variables definiujemy zmienne, które zostały wcześniej użyte w skprompt.txt, takie jak $source_language, $target_language, oraz $text_to_translate. Każda z tych zmiennych ma szczegółowy opis, co ułatwia ich użycie i zrozumienie przez innych programistów.

Czas połączyć wszystkie elementy razem i uruchomić nasz prompt. Poniższy kod w języku C# pokazuje, jak wykorzystać funkcje promptów oparte na plikach.

using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;

var builder = new ConfigurationBuilder()
    .AddUserSecrets<Program>();

var configuration = builder.Build();
var kernel = Kernel.CreateBuilder()
    .AddOpenAIChatCompletion("gpt-4o-mini", configuration["apiKey"]!)
    .Build();

var path = Path.Combine(Directory.GetCurrentDirectory(), "..","..","..", "Prompts");
var prompts = kernel.CreatePluginFromPromptDirectory(path);

var result = await kernel.InvokeAsync(prompts["Translate"], new KernelArguments
{
    ["source_language"] = "polish",
    ["target_language"] = "english",
    ["text_to_translate"] = "Pracuj tak ciężko jak to możliwe, to zwiększa szanse na sukces. Jeśli inni ludzie pracują 40 godzin w tygodniu, a ty 100 godzin, to ty w 4 miesiące osiągniesz to, co innym zajmie rok."
}) ;

Console.WriteLine(result);
Console.ReadLine();

Na początku kodu tworzony jest ConfigurationBuilder, który odpowiada za zbudowanie konfiguracji aplikacji, w tym uzyskanie klucza API z UserSecrets. To pozwala na bezpieczne przechowywanie danych uwierzytelniających. Następnie, używając Kernel.CreateBuilder(), tworzymy instancję kernel z dodanym wsparciem dla modelu OpenAI, używając klucza API (configuration["apiKey"]!). Dzięki temu nasz model (gpt-4o-mini) jest gotowy do generowania odpowiedzi.

Kolejnym krokiem jest załadowanie promptów z systemu plików. Ścieżka do katalogu z plikami promptów jest ustalana za pomocą Path.Combine(), co umożliwia załadunek ich przy użyciu kernel.CreatePluginFromPromptDirectory(path). W ten sposób pliki promptów stają się częścią środowiska Semantic Kernel jako pluginy, które można wywoływać w kodzie. Funkcje promptu są umieszczone w katalogu źródłowym Prompts/Translate, gdzie Translate jest nazwą funkcji promptu.

W następnej części kodu wykorzystujemy kernel.InvokeAsync(), aby uruchomić prompt Translate. Przekazywane są do niego zmienne, takie jak source_language (język źródłowy), target_language (język docelowy) oraz text_to_translate (tekst do przetłumaczenia). W tym przypadku chcemy przetłumaczyć zdanie z polskiego na angielski. Przekazane zmienne dynamicznie zastępują te użyte w definicji promptu w pliku skprompt.txt, co umożliwia elastyczne użycie tego samego promptu w różnych kontekstach.

Na koniec wynik tłumaczenia jest wyświetlany na ekranie konsoli za pomocą Console.WriteLine(result). Console.ReadLine() zatrzymuje program, aby użytkownik mógł przeanalizować wynik przed zakończeniem działania aplikacji. Ten kod pokazuje, jak łatwo połączyć konfigurację, prompty i wywoływanie funkcji w sposób modularny, co sprawia, że zarządzanie kodem jest bardziej efektywne i przejrzyste.

Zakończenie

Przechowywanie funkcji promptów w plikach to niezwykle praktyczne podejście, które zwiększa modularność, elastyczność i efektywność pracy z Semantic Kernel. Dzięki oddzieleniu treści promptów od kodu możemy łatwiej zarządzać projektami, ułatwić współpracę w zespole i zapewnić spójność w różnych aplikacjach. Takie rozwiązanie pozwala na dynamiczne i skalowalne tworzenie interakcji z modelami AI, co jest kluczowe w szybko rozwijającym się środowisku sztucznej inteligencji. Zachęcam do wypróbowania tego podejścia w swoich projektach, by przekonać się o jego zaletach w praktyce.

Do zobaczenia w kolejnych postach!