実はOracleも出している...!? コード生成AI "Oracle Code Assist" とは？

# はじめに

この記事は、[Oracle Cloud Infrastructure Advent Calender 2024 シリーズ 1 Day20](https://qiita.com/advent-calendar/2024/oci)の記事として書いています。

皆様は[`Oracle Code Assist`](https://www.publickey1.jp/blog/24/javaaioracle_code_assistoracle_cloudworld_2024.html)をご存知でしょうか？
実はOracleもコード生成AIを出しています。
まだ一般公開はされていませんが、絶賛開発中のプロダクトです。
現状は`β版`として、申請をすると一般ユーザの皆様にもご利用いただけます。

この記事では、そんなOracle Code Assistについて少し触れたいと思います。

# Oracle Code Assistとは

[Oracle Code Assist](https://www.oracle.com/jp/application-development/code-assist/)は、OCIにおけるJava、JavaScript、PL/SQLおよびアプリケーション開発に最適化されたコード生成AIツールです。

現状`β版`として提供されており、[こちら](https://blogs.oracle.com/developers/post/announcing-oracle-code-assist-beta-and-netsuite-suitescript-support)からお申し込み頂けます。[^1]

[^1]: Oracleプロファイルが必要です。また、承認プロセスがあります。このベータプログラムは情報提供のみを目的としており、機能の提供を約束するものではなく、購入の決定を行う際に依拠すべきものではありません。

サポートするIDEは以下の2種類です

- Visual Studio Code
- IntelliJ IDEA

IntelliJ IDEAプラグインが先行して開発されています。

# Oracle Code Assistのインストール

β版を取得すると、それぞれVSCodeとIntelliJのプラグインとしてzipファイルとvsixファイルがダウンロードできます。

## VSCodeのインストール

VSCodeでは拡張機能インストール画面からインストールします。

![CleanShot 2024-12-20 at 16.07.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/b0f000b5-7f0a-9cca-41d4-e1c9ac2cca31.png)

`VSIXからのインストール`をクリックし、取得したVSIXファイルを選択します。

インストールがされると以下のアイコンがサイドメニューに表示されます。

![CleanShot 2024-12-20 at 16.10.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/f4ac2919-9242-1d82-a8ab-90e4640da803.png)

こちらをクリックすると、以下のようにチャット画面が表示されます。

![CleanShot 2024-12-20 at 16.10.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/a1f178c4-ed11-a54a-abda-eb9a1962a9cd.png)

## IntelliJ IDEAでのインストール

 IntelliJ IDEAではプラグインインストール画面からインストールします。

 設定画面から`プラグイン`を選択し、`ディスクからプラグインをインストール`を選択します。
 取得したzipファイルを選択します。
 
 ![CleanShot 2024-12-20 at 16.22.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/02abcb11-dc6a-982c-c3a7-3fe1a88a9315.png)

 インストールされると以下のアイコンがサイドバーに表示されます。

 ![CleanShot 2024-12-20 at 16.25.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/bb90d8bd-c52c-15a0-ef5d-1822e98c7570.png)

 クリックすると以下のようなチャット画面が表示されます。

 ![CleanShot 2024-12-20 at 16.29.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/fdb611bc-6a3b-f628-ffa1-649ce9d812fc.png)

 # IntelliJ IDEAでOracle Code Assistを試そう

今回はIntelliJ IDEAでOracle Code Assistを試してみます。

日本語対応もしているので、今回は日本でコード生成を試みます。

![CleanShot 2024-12-20 at 16.36.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/9ecb71f9-9219-c862-a1f7-093ae1cf4f07.png)

こんな感じでコードを出してくれました。

全体はこんなコードでした。

```java
package org.example;

import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
import com.oracle.bmc.streaming.StreamClient;
import com.oracle.bmc.streaming.model.*;
import com.oracle.bmc.streaming.requests.GetMessagesRequest;
import com.oracle.bmc.streaming.responses.GetMessagesResponse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class GetMessagesExample {

    public static void main(String[] args) throws IOException {
        // OCIの認証情報ファイル(.oci/config)のパスを指定します。
        String configFilePath = "~/.oci/config";

        // 認証情報プロバイダーを作成します。
        ConfigFileAuthenticationDetailsProvider provider =
                new ConfigFileAuthenticationDetailsProvider(configFilePath);

        // Stream Clientを作成します。
        StreamClient streamClient = new StreamClient(provider);

        // ストリームのOCIDを指定します。
        String streamOcid = "ocid1.stream.oc1.iad.<unique_id>";

        // メッセージを取得するリクエストを作成します。
        GetMessagesRequest request = GetMessagesRequest.builder()
                .streamId(streamOcid)
                .limit(10) // 取得するメッセージ数を指定します。
                .build();

        try {
            // メッセージを取得します。
            GetMessagesResponse response = streamClient.getMessages(request);

            // 取得したメッセージを処理します。
            List<Message> messages = response.getItems();
            for (Message message : messages) {
                System.out.println("Key: " + message.getKey());
                System.out.println("Value: " + message.getValue());
            }
        } catch (Exception e) {
            System.err.println("Error getting messages: " + e.getMessage());
        } finally {
            // Stream Clientを閉じます。
            streamClient.close();
        }
    }
}
```

実は最近のOCI SDKでは非推奨のコードが含まれていたりしますが、そこはLLMの情報が古いということにしておきましょう...

Oracle Code Assistでは、以下のようにクラスやメソッドの上部に複数のラベルが表示されます。

![CleanShot 2024-12-20 at 17.06.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40392/556b8056-1cbb-7021-7845-5f0c229a4044.png)

以下の機能が提供されているようです。

- テストコード生成
- コードドキュメント（Javaで言えばJavaDoc）やコードコメントの生成
- クラスの説明
- Chatへの対象コードのコピーアンドペースト

今回はテストコード生成を試してみます。

`Generate Unit Test`をクリックするとチャット画面に以下のようなコードが生成されました。

```java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

public class GetMessagesExampleTest {

    private MockedStatic<ConfigFileAuthenticationDetailsProvider> configFileAuthenticationDetailsProviderMockedStatic;
    private MockedStatic<StreamClient> streamClientMockedStatic;

    private ByteArrayOutputStream outContent;
    private ByteArrayOutputStream errContent;

    @BeforeEach
    public void setUp() {
        configFileAuthenticationDetailsProviderMockedStatic = Mockito.mockStatic(ConfigFileAuthenticationDetailsProvider.class);
        streamClientMockedStatic = Mockito.mockStatic(StreamClient.class);

        outContent = new ByteArrayOutputStream();
        errContent = new ByteArrayOutputStream();
        System.setOut(new PrintStream(outContent));
        System.setErr(new PrintStream(errContent));
    }

    @AfterEach
    public void tearDown() {
        configFileAuthenticationDetailsProviderMockedStatic.close();
        streamClientMockedStatic.close();
        System.setOut(System.out);
        System.setErr(System.err);
    }

    @Test
    public void testMainSuccess() throws Exception {
        // Arrange
        var provider = mock(ConfigFileAuthenticationDetailsProvider.class);
        configFileAuthenticationDetailsProviderMockedStatic.when(() -> ConfigFileAuthenticationDetailsProvider.builder().build()).thenReturn(provider);

        var streamClient = mock(StreamClient.class);
        streamClientMockedStatic.when(() -> new StreamClient(any())).thenReturn(streamClient);

        var getMessagesResponse = mock(GetMessagesResponse.class);
        when(streamClient.getMessages(any())).thenReturn(getMessagesResponse);

        var messages = new ArrayList<>();
        messages.add(mock(Message.class));
        when(getMessagesResponse.getItems()).thenReturn(messages);

        // Act
        String[] args = {};
        GetMessagesExample.main(args);

        // Assert
        assertEquals("Key: " + messages.get(0).getKey() + "\n" +
                "Value: " + messages.get(0).getValue() + "\n", outContent.toString());
        verify(streamClient, times(1)).close();
    }

    @Test
    public void testMainFailure() throws Exception {
        // Arrange
        var provider = mock(ConfigFileAuthenticationDetailsProvider.class);
        configFileAuthenticationDetailsProviderMockedStatic.when(() -> ConfigFileAuthenticationDetailsProvider.builder().build()).thenReturn(provider);

        var streamClient = mock(StreamClient.class);
        streamClientMockedStatic.when(() -> new StreamClient(any())).thenReturn(streamClient);

        when(streamClient.getMessages(any())).thenThrow(new RuntimeException("Error"));

        // Act
        String[] args = {};
        GetMessagesExample.main(args);

        // Assert
        assertEquals("Error getting messages: Error\n", errContent.toString());
        verify(streamClient, times(1)).close();
    }

    @Test
    public void testMainEmptyMessages() throws Exception {
        // Arrange
        var provider = mock(ConfigFileAuthenticationDetailsProvider.class);
        configFileAuthenticationDetailsProviderMockedStatic.when(() -> ConfigFileAuthenticationDetailsProvider.builder().build()).thenReturn(provider);

        var streamClient = mock(StreamClient.class);
        streamClientMockedStatic.when(() -> new StreamClient(any())).thenReturn(streamClient);

        var getMessagesResponse = mock(GetMessagesResponse.class);
        when(streamClient.getMessages(any())).thenReturn(getMessagesResponse);

        when(getMessagesResponse.getItems()).thenReturn(null);

        // Act
        String[] args = {};
        GetMessagesExample.main(args);

        // Assert
        assertEquals("No messages found.\n", outContent.toString());
        verify(streamClient, times(1)).close();
    }

    @Test
    public void testMainIOException() {
        // Arrange
        assertThrows(IOException.class, () -> {
            // Act
            String[] args = {"invalid"};
            GetMessagesExample.main(args);
        });
    }
}
```

異常系も含まれていて、思ったよりしっかりしたテストコードが生成されていますね!!

# おわりに

今回はまだβ版ではありますが、Oracleのコード生成AIである`Oracle Code Assist`を紹介しました！
まだまだ発展途上ではあるので、未完成な部分もありますが、今度の発展に期待ですね！
皆様からのフィードバックもお待ちしておりますので、お時間がある方はベータプログラムに参加申請して頂き、ぜひ忌憚なきご意見をお寄せいただけるとありがたいです!!

# 参考

https://blogs.oracle.com/developers/post/announcing-oracle-code-assist-beta-and-netsuite-suitescript-support