ChatGPT API で C# でコマンドラインで会話する (人格を設定)

# ChatGPT API で C# でコマンドラインで会話する (人格を設定)

こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 22.04 で ChatGPT API を C# から使いコマンドラインから会話する方法を紹介します。
![chat-gpt_with_csharp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/e76ecf1d-a6e7-9f9e-75bf-8c291cf1eb6a.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境の Ubuntu で、ChatGPT API を C# から使いコマンドラインから会話します。

:::note warn
この記事では[前回の記事](https://qiita.com/studio_meowtoon/items/2675b596191e4fa2d0e3)の続きより、ChatGPT に人格を設定してみます。
:::

:::note alert
C# には本来[厳格なコーディング規則](https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/coding-style/coding-conventions)がありますが、この記事では可読性のために、一部規則に沿わない表記方法を使用しています。ご注意ください。
:::

## 関連記事

**OpenAI API** と比較してみましょう！

#### OpenAI API

https://qiita.com/studio_meowtoon/items/2960383dc750ba761362

## 技術トピック

### ChatGPT API とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [ChatGPT API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)
ChatGPT API は、OpenAI の言語モデルである GPT-3 をベースにした、自然言語による対話を実現するためのAPIです。

:::note info

|特徴|
|:--|
|ChatGPT API を利用することで、開発者は自分たちのアプリケーションやサービスに自然言語の対話機能を追加することができます。|
|ChatGPT API は、大規模なトレーニングデータを用いて学習された GPT-3 の言語理解力を利用し、自然言語での質問や会話に対して自然で流暢な回答を生成することができます。|
|また、ChatGPT API は、OpenAI が提供するプラットフォームである OpenAI Codex と組み合わせて、より高度な対話型アプリケーションを構築することも可能です。|

:::
</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

.NET SDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/d68031c91462f6808dff)

```sh
$ dotnet --list-sdks
7.0.202 [/usr/share/dotnet/sdk]
$ dotnet --version
7.0.202
```

</details>

:::note warn
この記事では基本的に Ubuntu のターミナルで操作を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## ChatGPT と会話する手順

### プロジェクトの作成

こちらの関連記事で手順がご確認いただけます。

https://qiita.com/studio_meowtoon/items/2675b596191e4fa2d0e3

**Ubuntu** に **OPENAI_API_KEY** 環境変数を作成します。

```sh
$ export OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

:::note alert
この環境変数が**一時的なもの**であることに注意してください。
:::

プロジェクトルートフォルダに移動します。
※ ~/tmp/ChatGPTApp をプロジェクトフォルダとします。

```sh
$ cd ~/tmp/ChatGPTApp
```

コンソールアプリをビルド・実行します。

```txt
$ dotnet run
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/fcf11f3b-23cb-44d2-dbc7-667640ddbbd4.png)

:::note info
ここまでの手順で、ChatGPT API を使い **ChatGPT** と会話を楽しむプログラムを作成することができました。
:::

:::note warn
しかし、デフォルトの状態では ChatGPT が少し**難しい言葉**を話します。また会話としては返答が**少し長い**ようです。
:::

## コードの修正 その1

**ChatGPT** に初期の設定を追加します。

Program.cs を修正します。

```sh
$ vim Program.cs
```

```csharp:Program.cs ※一部抜粋
// create a system content for the api.
string system = @"
    I am Japanese and a learner of English conversation.
    You must act on your role as a person who is having the short chat with the user.
    You only use the most simple English words should be used.
    I want to enjoy the short chat possible as you can.
    So you must keep your reply to use a minimum of sentences.
    You must remember the words I said to reflect on our short chat.
    Do you understand? Here we go!
";
```

:::note info
**ChatGPT API** の **system** パラメータで **ChatGPT** に対して、自分は日本人だけど英語での会話を楽しみたいから、やさしい内容で話してくれるようにお願いしています。
:::

<details>
<summary>ファイルの内容を表示します。</summary>

ファイルの内容

```csharp:Program.cs
using static System.Console;
using static System.ConsoleColor;
using static System.Environment;
using static System.Math;
using OpenAI_API;
using OpenAI_API.Chat;
using OpenAI_API.Models;
namespace ChatGPTApp {
    class Program {
        static int LIST_SIZE = 5;
        static async Task Main(string[] args) {
            // get an api key.
            string? api_key = GetEnvironmentVariable("OPENAI_API_KEY");
            // create an api object.
            OpenAIAPI api = new(api_key);
            // create a user and assistant list.
            List<MessageRole> list = new(LIST_SIZE);
            // loop until a prompt is empty.
            string? prompt = null;
            do {
                // read a prompt from the console.
                ForegroundColor = Yellow;
                WriteLine("Enter a prompt (or press Ctrl + C to exit):");
                ResetColor();
                prompt = ReadLine();
                if (!string.IsNullOrEmpty(prompt)) {
                    // create a system content for the api.
                    string system = @"
                        I am Japanese and a learner of English conversation.
                        You must act on your role as a person who is having the short chat with the user.
                        You only use the most simple English words should be used.
                        I want to enjoy the short chat possible as you can.
                        So you must keep your reply to use a minimum of sentences.
                        You must remember the words I said to reflect on our short chat.
                        Do you understand? Here we go!
                    ";
#if DEBUG
                    ForegroundColor = Red;
                    WriteLine($"SYSTEM:\n{system}\n");
                    ResetColor();
#endif
                    // set a system content.
                    List<ChatMessage> chat_message_list = new();
                    chat_message_list.Add(new ChatMessage(ChatMessageRole.System, system));
                    // set previous user and assistant contents.
                    list.ForEach(x => {
                        chat_message_list.Add(new ChatMessage(ChatMessageRole.User, x.User));
                        chat_message_list.Add(new ChatMessage(ChatMessageRole.Assistant, x.Assistant));
                    });
                    // set current user content.
                    chat_message_list.Add(new ChatMessage(ChatMessageRole.User, prompt));
                    // get a result from the api.
                    ChatResult result = await api.Chat.CreateChatCompletionAsync(new ChatRequest() {
                        Model = Model.ChatGPTTurbo0301,
                        Messages = chat_message_list
                    });
                    // print the result.
                    ForegroundColor = Green;
                    string reply = result.Choices[0].Message.Content.Trim();
                    WriteLine(reply);
                    // set conversation to a message_role object.
                    list.Add(new MessageRole{ User = prompt, Assistant = reply });
                    list = list.Skip(Max(0, list.Count - LIST_SIZE)).ToList();
#if DEBUG
                    ForegroundColor = Red;
                    list.ForEach(x => {
                        WriteLine($"USER: {x.User}");
                        WriteLine($"AI  : {x.Assistant}");
                    });
                    ResetColor();
#endif
                }
            } while (!string.IsNullOrEmpty(prompt));
            // wait the console.
            ReadLine();
        }
        class MessageRole {
            public string? User { get; set; }
            public string? Assistant { get; set; }
        }
    }
}
```

</details>

コンソールアプリをビルド・実行します。

```txt
$ dotnet run --configuration Release
```

:::note warn
以下のコマンドで実行するとデバッグ表示が出力されます。
```txt
$ dotnet run
```
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/d86c2638-3482-9e94-1148-0f8fe07f4eb0.png)

:::note info
ここまでの手順で、デフォルトの少々小難しい **ChatGPT** を、フレンドリーな性格に変えることができました。
:::
:::note warn
しかし、現在のプログラムでは **ChatGPT API** の **system** パラメータがプログラムにハードコーディングされています。
:::

## コードの修正 その2

**パラメータ**を **appsettings.json** 設定ファイルに記述します。

**Microsoft.Extensions.Configuration** のパッケージを [**NuGet**](https://learn.microsoft.com/ja-jp/nuget/what-is-nuget) で取得します。

```sh
$ dotnet add package Microsoft.Extensions.Configuration
$ dotnet add package Microsoft.Extensions.Configuration.Json
$ dotnet add package Microsoft.Extensions.Configuration.Binder
```

パッケージを確認します。

```sh
$ dotnet list package
プロジェクト 'ChatGPTApp' に次のパッケージ参照が含まれています
   [net7.0]:
   最上位レベル パッケージ                                     要求済み    解決済み
   > Microsoft.Extensions.Configuration             7.0.0   7.0.0
   > Microsoft.Extensions.Configuration.Binder      7.0.4   7.0.4
   > Microsoft.Extensions.Configuration.Json        7.0.0   7.0.0
   > OpenAI                                         1.7.2   1.7.2
```

AppSettings.cs を作成します。

```
$ vim AppSettings.cs
```

<details>
<summary>ファイルの内容を表示します。</summary>

ファイルの内容

```csharp:AppSettings.cs
namespace ChatGPTApp {
    public class AppSettings {
        public List<Persona>? Personas { get; set; }
    }
    public class Persona {
        public int? ListSize { get; set; }
        public string? System { get; set; }
    }
}
```

</details>

appsettings.json を作成します。

```sh
$ vim appsettings.json
```

ファイルの内容

```json:appsettings.json
{
  "Personas": [
    {
      "ListSize": 5,
      "System": "I am Japanese and a learner of English conversation.\nYou must act on your role as a person who is having the short chat with the user.\nYou only use the most simple English words should be used.\nI want to enjoy the short chat possible as you can.\nSo you must keep your reply to use a minimum of sentences.\nYou must remember the words I said to reflect on our short chat.\nDo you understand? Here we go!"
    }
  ]
}
```

:::note info
この設定ファイルの **ChatGPT API** の **system** パラメータに、**ChatGPT** に演じてほしい設定、性格を記述します。※日本語での記述も可能です。 
:::

ChatGPTApp.csproj を修正します。

```sh
$ vim ChatGPTApp.csproj
```

ファイルの内容

```sh
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
    <PackageReference Include="OpenAI" Version="1.7.2" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>
```

:::note warn
アプリのビルド時に **appsettings.json** を成果物に加えるように設定しています。
:::

Program.cs を修正します。

```sh
$ vim Program.cs
```

<details>
<summary>ファイルの内容を表示します。</summary>

ファイルの内容

```csharp:Program.cs
using static System.Console;
using static System.ConsoleColor;
using static System.Environment;
using static System.Math;
using Microsoft.Extensions.Configuration;
using OpenAI_API;
using OpenAI_API.Chat;
using OpenAI_API.Models;
namespace ChatGPTApp {
    class Program {
        static int DEFAULT_LIST_SIZE = 5;
        static async Task Main(string[] args) {
            // load an appsettings.
            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();
            AppSettings? app_settings = configuration.Get<AppSettings>();
            // get an api key.
            string? api_key = GetEnvironmentVariable("OPENAI_API_KEY");
            // create an api object.
            OpenAIAPI api = new(api_key);
            // create a user and assistant list.
            List<MessageRole> list = new((int) (app_settings?.Personas?[0].ListSize ?? DEFAULT_LIST_SIZE));
            // loop until a prompt is empty.
            string? prompt = null;
            do {
                // read a prompt from the console.
                ForegroundColor = Yellow;
                WriteLine("Enter a prompt (or press Ctrl + C to exit):");
                ResetColor();
                prompt = ReadLine();
                if (!string.IsNullOrEmpty(prompt)) {
                    // create a system content for the api.
                    string? system = app_settings?.Personas?[0].System;
#if DEBUG
                    ForegroundColor = Red;
                    WriteLine($"SYSTEM:\n{system}\n");
                    ResetColor();
#endif
                    // set a system content.
                    List<ChatMessage> chat_message_list = new();
                    chat_message_list.Add(new ChatMessage(ChatMessageRole.System, system));
                    // set previous user and assistant contents.
                    list.ForEach(x => {
                        chat_message_list.Add(new ChatMessage(ChatMessageRole.User, x.User));
                        chat_message_list.Add(new ChatMessage(ChatMessageRole.Assistant, x.Assistant));
                    });
                    // set current user content.
                    chat_message_list.Add(new ChatMessage(ChatMessageRole.User, prompt));
                    // get a result from the api.
                    ChatResult result = await api.Chat.CreateChatCompletionAsync(new ChatRequest() {
                        Model = Model.ChatGPTTurbo0301,
                        Messages = chat_message_list
                    });
                    // print the result.
                    ForegroundColor = Green;
                    string? reply = result.Choices[0].Message.Content.Trim();
                    WriteLine(reply);
                    // set conversation to a message_role object.
                    list.Add(new MessageRole{ User = prompt, Assistant = reply });
                    list = list.Skip(Max(0, list.Count - (int) (app_settings?.Personas?[0].ListSize ?? DEFAULT_LIST_SIZE))).ToList();
#if DEBUG
                    ForegroundColor = Red;
                    list.ForEach(x => {
                        WriteLine($"USER: {x.User}");
                        WriteLine($"AI  : {x.Assistant}");
                    });
                    ResetColor();
#endif
                }
            } while (!string.IsNullOrEmpty(prompt));
            // wait the console.
            ReadLine();
        }
        class MessageRole {
            public string? User { get; set; }
            public string? Assistant { get; set; }
        }
    }
}
```

</details>

コンソールアプリをビルド・実行します。

```txt
$ dotnet run --configuration Release
```

<details>
<summary>下の例の <b>ChatGPT API system</b> の内容を表示します。<br/>(※ 効果をわかりやすくするための設定をしています。ご了承ください。)</summary>

```json:appsettings.json
{
  "Personals": [
    {
      "ListSize": 5,
      "System": "あなたは日本の16才の高校生の女のコです。 \n簡単な言葉しか話しません、敬語は話しません。 \n語尾にはすべて だっちゃ。 を付けてください。\nでは演じてください。"
    }
  ]
}
```

</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/01cdbb82-9734-bedb-059d-d6a1cd256a86.png)

:::note info
ここまでの手順で、**appsettings.json** を作成してパラメータを設定することができました。また、そこに ChatGPT に**演じてもらいたい性格** を記述することにより、ChatGPT との会話を**より楽しむ**ことができます。
:::

## まとめ

ローカル環境の Ubuntu で、ChatGPT API を C# から使いコマンドラインから会話することができました。

:::note warn
実際の開発では、軽量なテキストエディタである **VS Code** や、**IDE (統合開発環境)** を使用して、.NET プログラムを開発することが一般的です。しかし、dotnet コマンドでビルドしたり、実行したりすることも、.NET 開発環境を理解する上で役立ちます。
:::

どうでしたか？ Window 11 の WSL Ubuntu に、.NET の開発環境を手軽に構築することができます。ぜひお試しください。今後も .NET の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/7cb530b19d8647d840e6

## 参考資料

https://github.com/OkGoDoIt/OpenAI-API-dotnet