ChatGPTより賢く質問に答えれるチャットBotを作る(誇張表現)

ChatGPTは2021年9月までのWebテキストで学習しているので2022年の知識を持っておらず、ワールドカップ2022年大会の優勝者を知らなかったりします。
BingではWeb検索と組み合わせることで解決しているので、それを自分で作ってしまえばいいのでは、とやってみました。

ちなみにやっていることは次のような流れ。

  1. GPTで質問を検索キーワードに変換
  2. そのキーワードでGoogle検索
  3. 検索結果1位のサイトにアクセス
  4. アクセス結果をGPTで要約

GPTを使う部分を説明すると、1.ではEdit APIを使っています。

var editReq = EditRequest.builder().input(text)
        .instruction("質問に答えるためのWeb検索キーワードに変換")
        .temperature(0.4)
        .model("text-davinci-edit-001").build();
EditResult keyResult = service.createEdit(editReq);
var keyword = keyResult.getChoices().get(0).getText().trim();

そして4. ではCompletionを使います。

CompletionRequest completionRequest = CompletionRequest.builder()
        .model("text-davinci-003") // davinci-003, curry, babbage, ada
        .prompt("「%s」という質問に答えるよう要約してください\n\n%s\n"
                .formatted(text, cArticle))
        .echo(false)
        .temperature(0.5)
        .user("testing")
        .maxTokens(500)
        .build();
CompletionResult result = service.createCompletion(completionRequest);  
var resultText = result.getChoices().get(0).getText(); 

ここでGPTに与える指示は完全に日本語であるのが面白いと思います。

2.のGoogle検索はこんな感じで。Jacksonがrecordに対応したのでマッピング作るのが楽になりましたね。

public class GoogleSearch {
    static String getApiKey() {
        return System.getenv("GOOGLE_API_KEY");
    }
    static String getCx() {
        return System.getenv("GOOGLE_SEARCH_CX");
    }

    record SearchItem(String kind, String title, String htmlTitle, String link) {}
    record SearchUrl(String type, String template){}
    record SearchResult(String kind, SearchUrl url, List<SearchItem> items){}
    
    static SearchItem search(String keyword) {
        try {
            var om = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            var url = "https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s".formatted(
                    getApiKey(), getCx(), URLEncoder.encode(keyword, "utf-8"));
            SearchResult result = om.readValue(new URL(url), SearchResult.class);
            return result.items().get(0);
        } catch(IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}

3.のサイトにアクセスする部分は数日前のブログに書いたコードでサイトの本文を取り出します。
Web記事の本文をJavaで抽出する - きしだのHatena

あとは、画面をこんな感じで作っておしまい。

public static void main(String[] args) {
    var f = new JFrame("AI検索");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(800, 600);

    var p = new JPanel(new BorderLayout());
    f.add(p, BorderLayout.NORTH);
    var t = new JTextField(30);
    p.add(t);
    var b = new JButton("検索");
    p.add(b, BorderLayout.EAST);
    var out = new JTextArea();
    out.setLineWrap(true);
    f.add(new JScrollPane(out));

    f.setVisible(true);
    OpenAiService service = new OpenAiService(getToken(), Duration.ZERO);
    b.addActionListener(al -> {
        b.setEnabled(false);
        new Thread(() -> {try {
            search(service, t.getText(), out);
        } finally {
            b.setEnabled(true);
        }}).start();
    });
}

こんな感じで動きます。2022年のワールドカップの優勝国を答えれていますね。

まあ、一番うまくいった事例という感じなので、基本的には使い物にならないんだけど、少ないコードでそれっぽいものができておもしろいです。
あと、GPTは前処理後処理に使って、前処理として自然言語DSLに変換、後処理として処理結果を自然言語に変換のようにするのがいいなと思いました。

ソースコード全体はこちらに
https://gist.github.com/kishida/1f6a61679dc3c9b4ecf2514aef81c02c