ChatGPT API で C# でコマンドラインで会話する (機能切り出し)
こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 22.04 で ChatGPT API を C# から使いコマンドラインから会話する方法を紹介します。
目的
Windows 11 の Linux でクラウド開発します。
こちらから記事の一覧がご覧いただけます。
実現すること
ローカル環境の Ubuntu で、ChatGPT API を C# から使いコマンドラインから会話します。
この記事では前回の記事の続きより、作成した機能をライブラリに切り出していきます。
C# には本来厳格なコーディング規則がありますが、この記事では可読性のために、一部規則に沿わない表記方法を使用しています。ご注意ください。
関連記事
OpenAI API と比較してみましょう!
OpenAI API
技術トピック
ChatGPT API とは?
こちらを展開してご覧いただけます。
ChatGPT API
ChatGPT API は、OpenAI の言語モデルである GPT-3 をベースにした、自然言語による対話を実現するためのAPIです。
特徴 |
---|
ChatGPT API を利用することで、開発者は自分たちのアプリケーションやサービスに自然言語の対話機能を追加することができます。 |
ChatGPT API は、大規模なトレーニングデータを用いて学習された GPT-3 の言語理解力を利用し、自然言語での質問や会話に対して自然で流暢な回答を生成することができます。 |
また、ChatGPT API は、OpenAI が提供するプラットフォームである OpenAI Codex と組み合わせて、より高度な対話型アプリケーションを構築することも可能です。 |
開発環境
- Windows 11 Home 22H2 を使用しています。
WSL の Ubuntu を操作していきますので macOS の方も参考にして頂けます。
WSL (Microsoft Store アプリ版) ※ こちらの関連記事からインストール方法をご確認いただけます
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
Ubuntu ※ こちらの関連記事からインストール方法をご確認いただけます
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
.NET SDK ※ こちらの関連記事からインストール方法をご確認いただけます
$ dotnet --list-sdks
7.0.202 [/usr/share/dotnet/sdk]
$ dotnet --version
7.0.202
この記事では基本的に Ubuntu のターミナルで操作を行います。Vim を使用してコピペする方法を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
ChatGPT と会話する手順
前回までにできたこと
ここまでの手順で、appsettings.json を作成してパラメータを設定することができました。また、そこに ChatGPT に演じてもらいたい性格 を記述することにより、ChatGPT との会話をより楽しむことができました。
今後、この ChatGPT API をカスタマイズしたサービスを Azure などのクラウド環境にデプロイすることを見据えて機能を再利用できるように切り出していきます。
ソリューションの作成
現在の ChatGPTApp プロジェクトを、App プロジェクトと Lib プロジェクトに切り分けます。
No | ソリューション/プロジェクト | 役わり | テンプレート |
---|---|---|---|
1 | ChatGPT/App | コマンドラインインタフェース | コンソール アプリ |
2 | ChatGPT/Lib | ライブラリ | クラス ライブラリ |
3 | ChatGPT/WebAPI ※ この記事では言及しません。 | Web API | ASP.NET Core Web API |
4 | ChatGPT/WebApp ※ この記事では言及しません。 | Web クライアント | Blazor WebAssembly |
ソリューションを作成します。
$ mkdir -p ~/tmp/ChatGPT
$ cd ~/tmp/ChatGPT
$ dotnet new sln -n ChatGPT
ここまでの手順で、ChatGPT という名前の .NET ソリューションが作成できました。
プロジェクトの移動
前回までに作成した ChatGPTApp プロジェクトをソリューションに移動します。
$ mv ~/tmp/ChatGPTApp ~/tmp/ChatGPT/App
App/ChatGPTApp.csproj をリネームします。
$ mv App/ChatGPTApp.csproj App/App.csproj
App プロジェクトを ChatGPT ソリューションに追加します。
$ dotnet sln ChatGPT.sln add App/App.csproj
プロジェクト `App/App.csproj` をソリューションに追加しました。
ソリューションを実行
前回までのアプリをソリューションとしてビルド、実行してみます。
Ubuntu に OPENAI_API_KEY 環境変数を作成します。
$ export OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
アプリをビルド・実行します。
$ dotnet run --project App/App.csproj --configuration Release
ここまでの手順で、前回までに作成したプロジェクトをソリューションに移動することができました。
クラス ライブラリ プロジェクトの作成
Lib プロジェクトを作成します。
$ dotnet new classlib -n Lib
必要なパッケージを NuGet で取得します。
$ dotnet add Lib/Lib.csproj package OpenAI
$ dotnet add Lib/Lib.csproj package Microsoft.Extensions.Configuration
$ dotnet add Lib/Lib.csproj package Microsoft.Extensions.Configuration.Json
$ dotnet add Lib/Lib.csproj package Microsoft.Extensions.Configuration.Binder
Lib プロジェクトを ChatGPT ソリューションに追加します。
$ dotnet sln ChatGPT.sln add Lib/Lib.csproj
プロジェクト `Lib/Lib.csproj` をソリューションに追加しました。
ソリューションの構成を確認します。
$ pwd
/home/$USER/tmp/ChatGPT
$ tree -I 'obj|bin'
.
├── App
│ ├── App.csproj
│ ├── AppSettings.cs
│ ├── Program.cs
│ └── appsettings.json
├── ChatGPT.sln
└── Lib
├── Class1.cs
└── Lib.csproj
ここまでの手順で、クラスライブラリ プロジェクトをソリューションに作成することができました。
コードの修正 その1
クラスライブラリに前回までのコードを切り出します。
Class1.cs をリネームします。
$ mv Lib/Class1.cs Lib/Service.cs
AppSettings.cs を移動します。
$ mv App/AppSettings.cs Lib/AppSettings.cs
AppSettings.cs を修正します。
$ vim Lib/AppSettings.cs
ファイルの内容を表示します。
ファイルの内容
namespace ChatGPT.Lib {
public class AppSettings {
public List<Persona>? Personas { get; set; }
}
public class Persona {
public int? ListSize { get; set; }
public string? System { get; set; }
}
}
Service.cs を修正します。
$ vim Lib/Service.cs
ファイルの内容を表示します。
ファイルの内容
using static System.Environment;
using static System.Math;
#if DEBUG
using static System.Console;
using static System.ConsoleColor;
#endif
using Microsoft.Extensions.Configuration;
using OpenAI_API;
using OpenAI_API.Chat;
using OpenAI_API.Models;
namespace ChatGPT.Lib {
public class Service {
static int DEFAULT_LIST_SIZE = 5;
AppSettings? _app_settings;
OpenAIAPI _api = new();
List<MessageRole> _list = new();
string? _prompt;
public string? Prompt { set { _prompt = value; }}
public event Changed? OnStart;
public event Changed? OnEnter;
public event Changed? OnResult;
public void Init() {
// load an appsettings.
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
_app_settings = configuration.Get<AppSettings>();
// get an api key.
string? api_key = GetEnvironmentVariable("OPENAI_API_KEY");
// create an api object.
_api = new(api_key);
// create a user and assistant list.
_list = new((int) (_app_settings?.Personas?[0].ListSize ?? DEFAULT_LIST_SIZE));
}
public async Task ExecuteAsync() {
// on start event.
OnStart?.Invoke(this, new EvtArgs("start") { Value = string.Empty });
// on enter event.
OnEnter?.Invoke(this, new("enter"));
// exec if a prompt is not empty.
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
});
// get a reply.
string? reply = result.Choices[0].Message.Content.Trim();
// on result event.
OnResult?.Invoke(this, new EvtArgs("result") { Value = 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
}
}
public class EvtArgs : EventArgs {
public EvtArgs(string name) {
Name = name;
}
public string Name { get; }
public string? Value { get; set; }
}
public delegate void Changed(object sender, EvtArgs e);
class MessageRole {
public string? User { get; set; }
public string? Assistant { get; set; }
}
}
}
ここまでの手順で、これまでに作成した処理をクラスライブラリに切り出すことができました。
コードの修正 その2
コンソールアプリの修正を行います。
App.csproj を修正します。
$ vim App/App.csproj
ファイルの内容
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Lib\Lib.csproj" />
</ItemGroup>
<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.6.0" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
App プロジェクトから、Lib プロジェクトを参照するように設定しています。
Program.cs を修正します。
$ vim App/Program.cs
ファイルの内容
using static System.Console;
using static System.ConsoleColor;
using ChatGPT.Lib;
namespace ChatGPT.App {
class Program {
static async Task Main(string[] args) {
// crate a service object.
Service service = new();
service.OnStart += (sender, e) => {
// print a message to input or exit.
ForegroundColor = Yellow;
WriteLine("Enter a prompt (or press Ctrl + C to exit):");
ResetColor();
};
service.OnEnter += (sender, e) => {
// read a prompt from the console.
((Service) sender).Prompt = ReadLine();
};
service.OnResult += (sender, e) => {
// print a result.
ForegroundColor = Green;
WriteLine(e.Value);
ResetColor();
};
// loop the Service object.
service.Init();
do {
await service.ExecuteAsync();
} while (true);
}
}
}
ソリューションをビルド・実行します。
$ dotnet run --project App/App.csproj --configuration Release
以下のコマンドで実行すると、デバッグ表示が出力されます。
$ dotnet run --project App/App.csproj
ここまでの手順で、ChatGPT API を使用するコードを、クラスライブラリに切り出すことができました。プログラムは前回までと同じように動作しています。この記事ではこれまでの機能を変更せずに処理を切り出したため、その効果が分かりにくいことをご了承ください。
まとめ
ローカル環境の Ubuntu で、ChatGPT API を C# から使いコマンドラインから会話することができました。
実際の開発では、軽量なテキストエディタである VS Code や、IDE (統合開発環境) を使用して、.NET プログラムを開発することが一般的です。しかし、dotnet コマンドでビルドしたり、実行したりすることも、.NET 開発環境を理解する上で役立ちます。
どうでしたか? Window 11 の WSL Ubuntu に、.NET の開発環境を手軽に構築することができます。ぜひお試しください。今後も .NET の開発環境などを紹介していきますので、ぜひお楽しみにしてください。
推奨コンテンツ
参考資料