本篇文章要測試上一篇文章寫好的架構,但是在那之前我們要來做一點簡單的修改,我們要結合之前製作的聊天室的畫面,並且針對 LightPlugin 裡面對功能的定義做修改,接著測試效果如何
記得一定要加上網路的權限
<uses-permission android:name="android.permission.INTERNET" />
這次將描述的部分調整為中文,並且將 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 "我不知道你在說什麼,請再說一次";
}
首先是變數以及一些基本設定的部分
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());
}
本次將描述的部分替換成中文,並且將一些容易產生誤會的地方重新定義後,測試的結果發現比原本都使用英文的描述來的更加準確,而直接在前端接 Semantic Kernel 的好處就是可以在 plugin 裡面放入真的要執行的程式碼,或者可以設定當功能被呼叫時就觸發發送一筆資料,再透過觀察資料的變化來做到控制的效果。