目次
・本日の成果・考え
・最後に
本日の成果
前回、クラス設計書に合わせて、ウィンドウの入力からDifyのAPIとの通信のやり取りの説明をしていませんでした。
変更があるかもですが、現段階での想定をお話しします。
通信としては以下の感じを考えています。
-
ウィンドウ内のJavaScriptからユーザー入力値をJson形式で渡してJavaメソッド起動
-
JavaBridgeで受信して、ロジッククラスに渡す。
※事前にWebViewオブジェクト内のJSObjectに起動したいJavaメソッドをセットしておくことで呼び出せるらしいです。 -
ロジッククラス内で、API通信クラスのリクエスト処理を呼び出す。
※リクエストヘッダーでAPIのURLやキーを付属させる。 -
BufferReaderクラスでチャンクごとに受信させる。
-
チャンク毎にウィンドウにメッセージを表示させるJavaScriptメソッドを起動させる。
-
全てのチャンク終了次第、終了フラグを出してJavaメソッド終了+ログ出力
以下を参考。
JSobject
OkHttpClient(HttpClientでなんとなく無難そうなので採用)
では、ソースになります。
package app.windowView.api;
import java.util.Map;
public class DifyRequestDto {
//
private final String query;
//
private final String response_mode = "streaming";
//共通関数のPCMACアドレス取得メソッドから取得。
private final String usrMacAddress;
//ユーザーチャット履歴追跡用ID 追加機能予定のため未使用
private final String conversation_id;
//ユーザーリクエストをフォーム項目として格納する用。 7/7時点で未使用。
private final Map<String, Object> inputs;
/**
*ユーザーメッセージを引数にするコンストラクタ 未使用項目(MACアドレス、チャット追跡ID、フォーム項目)あり。
* @param query ユーザーメッセージ、Jsonの項目名と同一
*/
public DifyRequestDto(String usrInputs) {
this.query = usrInputs;
this.usrMacAddress = null;
this.conversation_id = null;
this.inputs = null;
}
//各フィールドのgetterは省略
}
package app.windowView.api;
import java.util.List;
public class DifyResponseDto {
//
private String id;
//チャットbotの応答メッセージ
private String answer;
//メッセージID 未使用
private String message_id;
//チャット履歴追跡Id 未使用
private String conversation_id;
//チャットbotが参照したドキュメントの情報を格納するリスト。
private List<RetrieverResourceDto> retriever_resources;
//各フィールドのgetterは省略
}
DifyResponseDto.javaでsetterがないのは、Gsonで格納することが可能みたいだからです。
その代わり、Jsonのキーとフィールド変数が一致または、明示的にキーと宣言する必要があるみたいです。
また、private finalだと格納できなくなるので注意です。
package app.windowView.api;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import com.google.gson.Gson;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSource;
public class DifyApiClient {
private final String difyAPI_URL;
private final String apiKey;
private final OkHttpClient httpClient;
private final Gson gson = new Gson();
/**
*引数付きコンストラクタ
* @param 1:apiUrl API通信するURL 2:apiKey API通信のキー
*/
public DifyApiClient(String apiUrl, String apiKey) {
this.difyAPI_URL = apiUrl;
this.apiKey = apiKey;
this.httpClient = new OkHttpClient();
}
public void streamingMsg(DifyRequestDto dto, Consumer<String> onChunk, Runnable onComplete) {
//リクエストの中身
RequestBody body = RequestBody.create(
//送信データのメディアタイプををJson形式に設定。引数はテンプレ。
MediaType.parse("application/json"),
gson.toJson(dto).getBytes(StandardCharsets.UTF_8));
//リクエストのヘッダ+中身を融合
Request request = new Request.Builder()
.url(difyAPI_URL)
.header("Authorization", "Bearer" + apiKey)
.post(body)
.build();
//
httpClient.newCall(request).enqueue(new Callback() {
//通信失敗時の処理
@Override
public void onFailure(Call call, IOException e) {
throw new IllegalStateException("APIエラー:" + e.getMessage());
}
//通信が成功して何かしらのレスポンスを受信した時
@Override
public void onResponse(Call call, Response response) throws IOException {
// ステータスコードチェック(200系以外はNG)
if (!response.isSuccessful()) {
throw new IOException("API通信ステータスエラー;" + response.code());
}
//チャンク毎にレスポンスの受け取り
try (BufferedSource source = response.body().source()) {
while (!source.exhausted()) {
//1行ずつUTF-8形式で格納
String resline = source.readUtf8LineStrict();
if (resline.startsWith("data:")) {
//受信チャンクが終了文かどうかチェック
String data = resline.substring(6);
if ("[DONE]".equals(data)) {
onComplete.run();
break;
}
DifyResponseDto chunk = gson.fromJson(data, DifyResponseDto.class);
//メソッド引数のonChunkにセットされているメソッドの呼び出し。
onChunk.accept(chunk.getAnswer());
}
}
} catch (Exception e2) {
//チャンク読み込み中エラー
throw new IllegalStateException("チャンク読み込みエラー:" + e2.getMessage());
}
}
});
}
}
ここまでみていただいた方でお気づきの方がいらっしゃるかは不明ですが、今までのソースでは、何か例外をスローする時
throw new IllegalStateException("")
をなんとなく使っていました。
しかし、今回は通信の例外ということで、
throw new IOException
としました。
簡単に調べた限り以下のような使い分け
IOException | IllegalStateException | |
---|---|---|
カテゴリ | チェック例外 | 実行時例外 |
用途 | 通信・ファイル入出力のエラーなど | オブジェクトの状態遷移や処理の前提条件のエラーなど |
IOExceptionってファイルやDBの入出力以外にも使えるのかと知る機会になりました。
最後に
前の投稿から時間が空きましたが、サボっていたわけではなく作業を進めるためにJsonとAPI通信をJavaでどう実装するかを調べながらやっていたらかなり時間がかかりました。
一旦、API通信に必要な処理とDtoクラスのPGが完了したので、呼び出しが可能な動作確認して、UTに移行したいと思います。