iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0

前言

本篇文章要測試上一篇文章寫好的架構,但是在那之前我們要來做一點簡單的修改,我們要結合之前製作的聊天室的畫面,並且針對 LightPlugin 裡面對功能的定義做修改,接著測試效果如何

網路權限

記得一定要加上網路的權限

    <uses-permission android:name="android.permission.INTERNET" />

LightPlugin

這次將描述的部分調整為中文,並且將 isOn 從布林值調整成中文的開啟、關閉,之後便會測試效果會不會變好

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

    public LightPlugin() {
        lights.put(1, new LightModel(1, "Table Lamp", "關閉"));
        lights.put(2, new LightModel(2, "Porch light", "關閉"));
        lights.put(3, new LightModel(3, "Chandelier", "開啟"));
    }

    public static String getLights() {
        return lights.toString();
    }

    @DefineKernelFunction(name = "get_lights_state", description = "依據語句情境'取得'指定的燈光狀態")
    public List<LightModel> getLightsState() {
        System.out.println("Getting lights state");
        return new ArrayList<>(lights.values());
    }

    @DefineKernelFunction(name = "change_state", description = "依據語句情境'變更'指定的燈光狀態")
    public LightModel changeState(
            @KernelFunctionParameter(name = "id", description = "燈光的ID") int id,
            @KernelFunctionParameter(name = "isOn", description = "燈光需要變更的狀態") String 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);
    }

    @DefineKernelFunction(name = "added_new_light", description = "依據語句情境'新增'新的燈具")
    public LightModel addNewLight(
            @KernelFunctionParameter(name = "id", description = "燈光的ID") int id,
            @KernelFunctionParameter(name = "name", description = "燈光的名稱") String name,
            @KernelFunctionParameter(name = "isOn", description = "燈光的狀態") String isOn) {
        System.out.println("Adding new light " + id + " " + name + " " + isOn);
        if (lights.containsKey(id)) {
            throw new IllegalArgumentException("Light already exists");
        }

        lights.put(id, new LightModel(id, name, isOn));

        return lights.get(id);
    }

    @DefineKernelFunction(name = "OTHER", description = "當語意辨識不符合任何功能控制時,就已正常GPT對話回應")
    public String other() {
        return "我不知道你在說什麼,請再說一次";
    }

MainActivity

首先是變數以及一些基本設定的部分

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private List<ChatMessage> messages = new ArrayList<>();
    private ChatAdapter chatAdapter;
    private EditText messageEditText;
    private Button sendButton;
    private RecyclerView chatRecyclerView;

    // Semantic Kernel
    private final String modelId = "gpt-4o-mini";
    private final String OPEN_AI_KEY = "API KEY";
    private OpenAIAsyncClient client;
    private ChatCompletionService chatCompletionService;
    private Kernel kernel;
    private InvocationContext invocationContext;
    ChatHistory history = new ChatHistory();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindUI();

        setRecyclerView();

        setButtonClickListener();

        setSemanticKernel();
    }

    private void setRecyclerView() {
        chatAdapter = new ChatAdapter(messages);
        chatRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        chatRecyclerView.setAdapter(chatAdapter);
    }

    private void setButtonClickListener() {
        sendButton.setOnClickListener(v -> {
            String message = messageEditText.getText().toString();
            sendMessage(message);
        });

    }

    private void bindUI() {
        messageEditText = findViewById(R.id.messageEditText);
        sendButton = findViewById(R.id.sendButton);
        chatRecyclerView = findViewById(R.id.chatRecyclerView);
    }

    private void sendMessage(String message) {
        chatAdapter.addMessage(new ChatMessage(message, true));

        getSemanticKernelResponse(message);
    }

    private void receiveMessage(String message) {
        chatAdapter.addMessage(new ChatMessage(message, false));
    }

接著 Semantic Kernel 的部分也跟之前設定長的一樣

    private void setSemanticKernel() {
        client = new OpenAIClientBuilder()
                .credential(new AzureKeyCredential(OPEN_AI_KEY))
                .buildAsyncClient();

        chatCompletionService = OpenAIChatCompletion.builder()
                .withOpenAIAsyncClient(client)
                .withModelId(modelId)
                .build();

        KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightPlugin(),
                "LightsPlugin");

        kernel = Kernel.builder()
                .withAIService(ChatCompletionService.class, chatCompletionService)
                .withPlugin(lightPlugin)
                .build();

        ContextVariableTypes
                .addGlobalConverter(
                        ContextVariableTypeConverter.builder(LightModel.class)
                                .toPromptString(new Gson()::toJson)
                                .build());

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

最後就是功能的部分

這裡一樣在一開始使用時會先設定初始的狀態

    private void getSemanticKernelResponse(String message) {
        
        if (history.getMessages().isEmpty()) {
            history.addUserMessage("燈光狀態 = " + LightPlugin.getLights());
        }

        history.addUserMessage(message);

        List<ChatMessageContent<?>> results = chatCompletionService
                .getChatMessageContentsAsync(history, kernel, invocationContext)
                .block();

        for (ChatMessageContent<?> result : results) {
            if (!result.getContent().contains("error"))
                history.addMessage(result);
        }

        Log.e(TAG, "getSemanticKernelResponse: " + history.getMessages().get(history.getMessages().size() - 1).getContent());

        receiveMessage(history.getMessages().get(history.getMessages().size() - 1).getContent());
    }

測試效果

image

image

image

image

image
image

總結

本次將描述的部分替換成中文,並且將一些容易產生誤會的地方重新定義後,測試的結果發現比原本都使用英文的描述來的更加準確,而直接在前端接 Semantic Kernel 的好處就是可以在 plugin 裡面放入真的要執行的程式碼,或者可以設定當功能被呼叫時就觸發發送一筆資料,再透過觀察資料的變化來做到控制的效果。


上一篇
[DAY26] 怎麼直接在前端架設 Semantic Kernel
下一篇
[DAY28] 在前端架設 Semantic Kernel 2
系列文
智慧語義互動平台:基於Spring和Semantic Kernel的Android應用創新30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言