[ODC]生成AI（Gemini）を使って、External Logicを作成してみる

OutSystemsで生成AIというと、Mentor機能の一部（完全なODCアプリケーションを生成できる）や、外部のAIを利用できるAI Agent Builderなどがある。
他に生成AIを適用しやすそうな領域を考えていたら、External Logicは適用できそうな気がしたので、試しに作ってみた。

試してみた手順（生成AIをもっと使いこなすと、自動生成できる部分も増えそうだが）
1. 生成AIを使ってC#のファイル（interfaceとclass）と必要なNuGetパッケージインストールコマンドを生成する
1. 手順に沿って、ソリューションとプロジェクトを生成
1. interfaceとclassをプロジェクト内に移動、必要に応じて修正
1. Publish
1. 動作確認

# 先に試してみた感想
- C#の開発者にとっては、開発効率上げるのに明らかに有効
- 他の言語の開発者で、OutSystemsの知識はあるがC#の知識がない人にとっては、手順を整備すれば有効に使えそう
- 開発者でない人は、プロトタイプや今回試したようなシンプルな仕様ならなんとか使えそう
- より複雑な仕様でも試してみたが、コンパイルできないコードや動作しないコードが生成されたり、プロンプトで問題点を指摘しただけではうまく直してくれなかったりした。開発スキルを持たない人が手順を踏むだけでExternal Logicを作るのは難しそう
- 下に示したプロンプトの前提部分をもっとブラッシュアップすれば、手動介入が必要な場所は減らせそう

# 環境情報
OutSystemsExternalLibraries.SDK.1.5.0
.NET SDK 8.0
Visual Studio Code (Version 1.92.2) 
生成AIはGemini使用

作業はMac OSで実施

# 作成するExternal Logicの仕様
以下のActionを1つ持つExternal Logic。
入力：正の整数2つ
出力：小さい入力から大きい入力まで1つずカウントアップした値のリスト

# 1. 生成AIを使ってC#のファイル（interfaceとclass）と必要なNuGetパッケージインストールコマンドを生成する
## プロンプト
以下のようなプロンプトをGeminiに入力してみた。前提の部分は他の機能でも使い回し、仕様の部分だけ書き換えて使えないかと思って、分けて書いてみた。
``` プロンプト
次の条件を満たすC#のメソッドを作成してください。

前提
- .NET 8
- OSはLinux
- ソリューション及びプロジェクトの作成は不要
- NuGetにあるライブラリを使って良い（必要なNuGetライブラリについては、VSCode上でプロジェクトに追加するためのコマンドリストを生成すること）
- メソッドはinterfaceで定義し、そのinterfaceを実装する
- interfaceとclassは別ファイルで定義する
- interfaceには「OSInterface」属性を付与する
- interface内のメソッドには「OSAction」属性を付与する
- OSAction属性には、Descriptionプロパティ（メソッド機能の要点を設定）とReturnNameプロパティ（メソッドの戻り値が果たす役割から簡潔な名前を英語で設定）
- OSInterface及びOSAction属性は別の場所で定義したものを参照するので、定義部分は出力しない

仕様
- 正の整数を2つ受け取り
- 入力値が０以下であれば適切な例外をthrowする
- 小さい方の値から大きい方の値まで１ずつカウントアップしたListを返す
```

## 出力されたコード
注文通り、interfaceとclassが別ファイルとして出力されている。
なお、今回は属性定義（OSInterfaceとOSAction）は出力されなかった（リクエスト通り）。ただ、何度か試していると、プロンプトが無視されて出力されることの方が多かった。同じプロンプトを入れていたも微妙に結果が変わる事もある？

``` Interface1.cs
using System.Collections.Generic;

[OSInterface]
public interface ICountingService
{
    [OSAction(Description = "小さい方の値から大きい方の値まで1ずつカウントアップする", ReturnName = "CountingList")]
    List<int> CountUp(int start, int end);
}
```

こっちがclass定義。
``` Service1.cs
using System;
using System.Collections.Generic;

public class CountingService : ICountingService
{
    public List<int> CountUp(int start, int end)
    {
        if (start <= 0 || end <= 0)
        {
            throw new ArgumentException("入力値は正の整数である必要があります。");
        }

        var list = new List<int>();
        for (int i = Math.Min(start, end); i <= Math.Max(start, end); i++)
        {
            list.Add(i);
        }
        return list;
    }
}
```

見た感じ、

- ファイル・interface・class・メソッドの名付けは微妙（試している中で良さそう名前がつけられる事もあったのだが）→配置するときに手動で直すことにする
- OutSystems提供SDKの属性はうまくついている（ただし、ReturnNameについた名前はそれほど良くない。恐らくメソッド名等に引っ張られているのか）
- 実装はちゃんと動きそう（Range等を使ってもっとすっきり描けるとは思うが、今回は動作すれば良いと考えてそのままにする）

# 2. 手順に沿って、ソリューションとプロジェクトを生成

[[ODC]External LogicをVisual Studio Codeで作成する #C# - Qiita](https://qiita.com/jyunji_watanabe/items/04ffd0bc25ed4e845661)
のコマンドを利用して、プロジェクトを用意する。
ソリューション=QiitaSamples
プロジェクト=QiitaSamplesExternalLogic
.NET 8

``` 使用したコマンド
dotnet new sln -o QiitaSamples
cd QiitaSamples
dotnet new classlib -o QiitaSamplesExternalLogic --framework net8.0
dotnet sln add QiitaSamplesExternalLogic/QiitaSamplesExternalLogic.csproj
cd QiitaSamplesExternalLogic
dotnet add package OutSystems.ExternalLibraries.SDK
```

# 3. interfaceとclassをプロジェクト内に移動、必要に応じて修正
## 自動作成された余分なファイルを削除
Class1.csというファイルがあるが不要なので削除。

## interfaceを配置
↑に貼った、Interface1.csファイルを、作成したプロジェクト直下に配置する。

このファイルには、OutSystems提供の属性に対するusingがないので追加する。
赤線が出ている部分にカーソルを合わせて、「Command＋.」でQuick Fixを表示し、「using OutSystems.ExternalLibraries.SDK」を選択。
![スクリーンショット 2024-12-01 13.13.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/e7251c7e-9060-5714-2d61-ab37e064652c.png)

また、名前（ファイル名、interface名、メソッド名）を調整した。
結果のファイルが以下。

``` IQiitaSamples.cs
using System.Collections.Generic;
using OutSystems.ExternalLibraries.SDK;

[OSInterface]
public interface IQiitaSamples
{
    [OSAction(Description = "小さい方の値から大きい方の値まで1ずつカウントアップする", ReturnName = "CountingList")]
    List<int> Range(int start, int end);
}
```

## classを配置
↑に貼った、Service1.csファイルを、作成したプロジェクト直下に配置する。
こちらも同様に名前を修正。

``` QiitaSamples.cs
using System;
using System.Collections.Generic;

public class CountingService : IQiitaSamples
{
    public List<int> Range(int start, int end)
    {
        if (start <= 0 || end <= 0)
        {
            throw new ArgumentException("入力値は正の整数である必要があります。");
        }

        var list = new List<int>();
        for (int i = Math.Min(start, end); i <= Math.Max(start, end); i++)
        {
            list.Add(i);
        }
        return list;
    }
}
```

なお、プロンプトではNuGetライブラリを使う場合はインストールするためのコマンドも生成すること、としていたが、使っていないのでコマンドリストも作らない旨の回答が返ってきていた。

# 4. Publish
ここはExternal Logicとして共通の手順。

ターミナルで、ソリューションディレクトリを開き、以下のコマンドでPublish。

``` コマンド
dotnet publish -c Release -r linux-x64 --self-contained false
```

「/bin/Release/net8.0/linux-x64/publish/」ディレクトリ内の全ファイルをzipに圧縮。
ODC Portal > External logicを開き、「Create library」ボタンから登録。
（詳細な手順は、[[OutSystems]ODCでC#を取り込んでみる（External Logic） #outsystems - Qiita](https://qiita.com/jyunji_watanabe/items/2698fb5a80b857875a48#odc%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89)を参照）

# 5. 動作確認
アップロードしたExternal LogicをTry library in an App機能（使い方は↑のリンク参照）で参照し、以下の実装でテストしてみた。

![スクリーンショット 2024-12-01 13.44.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/eb209bd7-0564-73fb-577f-efd56b1523cf.png)

Output ParameterをJSON Serializeすると以下の値が取れ、想定通りに動いている。
```
[4,5,6,7,8,9,10,11,12]
```