iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0

前言

上篇文章講到實作 Semantic Kernel 前必須要擁有自己的 Open AI 的 API Key 才可以正常使用功能,這篇文章會講解官方附上的程式碼個別執行的工作

Semantic Kernel 官方範例

SDK

首先是 Semantic Kernel 的 SDK,目前最新的版本是 1.3.0

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.microsoft.semantic-kernel</groupId>
            <artifactId>semantickernel-bom</artifactId>
            <version>${sk.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
<dependency>
    <groupId>com.microsoft.semantic-kernel</groupId>
    <artifactId>semantickernel-api</artifactId>
</dependency>
<dependency>
    <groupId>com.microsoft.semantic-kernel</groupId>
    <artifactId>semantickernel-aiservices-openai</artifactId>
</dependency>
</dependencies>

Open AI 服務設定

這裡的寫法會跟官方文件不太一樣,這是因為官方範例是使用 Azure Open AI 當作語意辨識的 AI,但是我們使用的是 Chat GPT,而正常來說 Open AI 可以自動將 endpoint 引入正確的網址,因此這邊要將 endpoint() 給移除

接著可能會對 .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) 這個部分感到疑惑,既然都不是使用 Azure Open AI 了,為什麼還是使用 AzureKeyCredential 當作 API Key 的憑證呢?

關於這個問題簡單來說就是 Microsoft 並沒有額外添加 Open AI 的 Credential,只要使用同樣的 AzureKeyCredential 並填入自己的 Open AI API Key 就可以使用了

最後 MODEL_ID 就是填入 Open AI 目前釋出可以使用的模型,這次的專案中都是使用 gpt-4o-mini 模型

OpenAIAsyncClient client = new OpenAIClientBuilder()
    .credential(new AzureKeyCredential(AZURE_CLIENT_KEY))
    .buildAsyncClient();

// Create your AI service client
ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder()
    .withModelId(MODEL_ID)
    .withOpenAIAsyncClient(client)
    .build();

包裝 Plugin

在這個部分將自定義的 plugin 包裝成 Kernel 可以處理的結構,接著將這個 plugin 丟給 Kernel 註冊,讓 AI 可以辨識有這個功能可以使用

// Import the LightsPlugin
KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(),
    "LightsPlugin");

Kernel 設定

在這裡我們主要在做的事,是將自定義的功能加進 Kernel,讓我們的 AI 可以成功辨識需要執行的功能

當我們將自定義的功能寫好後,需要先將自定義的功能包裝好,接著需要添加到Kernel中,在官方範例中是建立了一個跟燈光有關的功能,稍後會介紹大概的寫法

最後的 InvocationContext,這個指定了每次回傳時的內容是只回最後一句

// Create a kernel with Azure OpenAI chat completion and plugin
Kernel kernel = Kernel.builder()
    .withAIService(ChatCompletionService.class, chatCompletionService)
    .withPlugin(lightPlugin)
    .build();

// Add a converter to the kernel to show it how to serialise LightModel objects into a prompt
ContextVariableTypes
    .addGlobalConverter(
        ContextVariableTypeConverter.builder(LightModel.class)
            .toPromptString(new Gson()::toJson)
            .build());

// Enable planning
InvocationContext invocationContext = new InvocationContext.Builder()
    .withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY)
    .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
    .build();

使用

最後這一大段都是啟用的方法,其中比較重要的有

  1. ChatHistory
  2. results

ChatHistory 代表的是這個對話的紀錄,也就是上下文的意思,可以選擇每次詢問 AI 後都會將詢問內容跟回話保存,套用到下次的對話中

results 代表的是回傳的內容,在這裡其實就是將跟 AI 溝通的部分變成一個 Function,並且設定這個對話的歷史紀錄、可以辨識的 kernel功能、回傳的內容,如此一來就省去自己寫 API 路徑,以及設定傳入的參數和接回傳內容的架構等等的功夫

接著就是針對這個 result 再 print 出回傳的文字就大功告成了 !

// Create a history to store the conversation
ChatHistory history = new ChatHistory();

// Initiate a back-and-forth chat
Scanner scanner = new Scanner(System.in);
String userInput;
do {
  // Collect user input
  System.out.print("User > ");

  userInput = scanner.nextLine();
  // Add user input
  history.addUserMessage(userInput);

  // Prompt AI for response to users input
  List<ChatMessageContent<?>> results = chatCompletionService
      .getChatMessageContentsAsync(history, kernel, invocationContext)
      .block();

  for (ChatMessageContent<?> result : results) {
    // Print the results
    if (result.getAuthorRole() == AuthorRole.ASSISTANT && result.getContent() != null) {
      System.out.println("Assistant > " + result);
    }
    // Add the message from the agent to the chat history
    history.addMessage(result);
  }
} while (userInput != null && !userInput.isEmpty());

LightsPlugin

我們要自定義一個功能的話,就要像這樣將功能的結構、功能寫好,基本寫法就向下方程式碼展示的一樣

其中比較重要的有

  • @DefineKernelFunction
    這個註解標記了這個 plugin 的方法,給予名字以及描述,要注意的是描述的部分一定要盡可能的準確,才可以避免有判斷錯誤的問題發生

  • @KernelFunctionParameter
    這個註解標記了這個 plugin 的方法需要的參數有什麼,這樣一來使用這個功能就需要將參數傳入,我們也就可以讓這個功能再進行更進階的判斷

public class LightsPlugin {

  // Mock data for the lights
  private final Map<Integer, LightModel> lights = new HashMap<>();

  public LightsPlugin() {
    lights.put(1, new LightModel(1, "Table Lamp", false));
    lights.put(2, new LightModel(2, "Porch light", false));
    lights.put(3, new LightModel(3, "Chandelier", true));
  }

  @DefineKernelFunction(name = "get_lights", description = "Gets a list of lights and their current state")
  public List<LightModel> getLights() {
    System.out.println("Getting lights");
    return new ArrayList<>(lights.values());
  }

  @DefineKernelFunction(name = "change_state", description = "Changes the state of the light")
  public LightModel changeState(
      @KernelFunctionParameter(name = "id", description = "The ID of the light to change") int id,
      @KernelFunctionParameter(name = "isOn", description = "The new state of the light") boolean isOn) {
    System.out.println("Changing light " + id + " " + isOn);
    if (!lights.containsKey(id)) {
      throw new IllegalArgumentException("Light not found");
    }

    lights.get(id).setIsOn(isOn);

    return lights.get(id);
  }
}

上一篇
[DAY19] Microsoft Semantic Kernel
系列文
智慧語義互動平台:基於Spring和Semantic Kernel的Android應用創新20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言