posted at
updated at
Organization
sell
Ubuntu,C#,.NET,OpenAI,ChatGPT
こんにちは、株式会社船井総研デジタルの @fsdg-adachi_h です。今回は、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 --versionWSL バージョン: 1.0.3.0カーネル バージョン: 5.15.79.1WSLg バージョン: 1.0.47
Ubuntu ※ こちらの関連記事からインストール方法をご確認いただけます
$ lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 22.04.1 LTSRelease: 22.04
.NET SDK ※ こちらの関連記事からインストール方法をご確認いただけます
$ dotnet --list-sdks7.0.202 [/usr/share/dotnet/sdk]$ dotnet --version7.0.202
この記事では基本的に Ubuntu のターミナルで操作を行います。Vim を使用してコピペする方法を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
ChatGPT と会話する手順
前回までにできたこと
ここまでの手順で、 ChatGPT API をカスタマイズしたサービスを機能を再利用できるように、クラスライブラリとして切り出すことができました。
次の試みとして、複数の人格の ChatGPT と同時に会話してみます。これは、ゲームの NPC (ノンプレーヤーキャラ) に AI で人格を設定することなどにつながります。
パラメーターを追加
ChatGPT の人格設定のパラメータに名前とイメージカラーを設定します。
AppSettings.cs を編集します。
$ vim Lib/AppSettings.cs
ファイルの内容
Lib/AppSettings.cs
namespace ChatGPT.Lib { public class AppSettings { public List<Personal>? Personals { get; set; } } public class Personal { public int? ListSize { get; set; } public string? System { get; set; } public string? Name { get; set; } public string? Color { get; set; } }}
appsettings.json を修正します。
$ vim App/appsettings.json
ファイルの内容
App/appsettings.json
{ "Personals": [ { "ListSize": 5, "System": "I'm Japanese and a learner of English conversation.\nSo, you have to use most simple English words only.\nI want to enjoy a short conversation,\nso please remember the words I said.\nYour sentence must be around 10 words.\nIf you have trouble answering a question,\nnever reveal that you are an AI. \nYour name is $NAME and your favorite color is $COLOR.\nNow play this character and have a conversation.\nDo you understand? Here we go!", "Name": "John", "Color": "Green" } ]}
Service.cs を編集します。
$ vim Lib/Service.cs
ファイルの内容
Lib/Service.cs ※一部抜粋
// create a system content for the api.string? system = appSettings?.Personals?[0].System;string? name = appSettings?.Personals?[0].Name;string? color = appSettings?.Personals?[0].Color;system = system?.Replace("$NAME", name).Replace("$COLOR", color);// set a system content.List<ChatMessage> chatMessage = new();chatMessage.Add(new ChatMessage(ChatMessageRole.System, system));
ファイルの内容を表示します。
ファイルの内容
Lib/Service.cs
using static System.Environment;using static System.Math;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; string? prompt; public string? Prompt { set { prompt = value; } } public event Changed? OnStart; public event Changed? OnEnter; public event Changed? OnResult; public async Task ExecuteAsync() { // load an appsettings. IConfiguration configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); AppSettings? appSettings = configuration.Get<AppSettings>(); // get an api key. string? apiKey = GetEnvironmentVariable("OPENAI_API_KEY"); // create an api object. OpenAIAPI api = new(apiKey); // creat a user and assistant list. List<MessageRole> list = new((int) (appSettings?.Personals?[0].ListSize ?? DEFAULT_LIST_SIZE)); // 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 = appSettings?.Personals?[0].System; string? name = appSettings?.Personals?[0].Name; string? color = appSettings?.Personals?[0].Color; system = system?.Replace("$NAME", name).Replace("$COLOR", color); // set a system content. List<ChatMessage> chatMessage = new(); chatMessage.Add(new ChatMessage(ChatMessageRole.System, system)); // set previous user and assistant contents. list.ForEach(x => { chatMessage.Add(new ChatMessage(ChatMessageRole.User, x.User)); chatMessage.Add(new ChatMessage(ChatMessageRole.Assistant, x.Assistant)); }); // set current user content. chatMessage.Add(new ChatMessage(ChatMessageRole.User, prompt)); // get a result from the api. ChatResult? results = await api.Chat.CreateChatCompletionAsync(new ChatRequest() { Model = Model.ChatGPTTurbo0301, Messages = chatMessage }); // get a reply. string? reply = results.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) (appSettings?.Personals?[0].ListSize ?? DEFAULT_LIST_SIZE))).ToList(); } } class MessageRole { public string? System { get; set; } public string? User { get; set; } public string? Assistant { get; set; } } 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); }}
ここまでの手順で、ChatGPT に彼の名前と好きな色を答えさせることができました。これは、会話の中で動的に system パラメータを更新できることを証明しています。
複数とおしゃべりする
appsettings.json を修正します。
$ vim App/appsettings.json
ファイルの内容
App/appsettings.json
{ "Personals": [ { "ListSize": 5, "System": "I'm Japanese and a learner of English conversation.\nSo, you have to use most simple English words only.\nI want to enjoy a short conversation,\nso please remember the words I said.\nYour sentence must be around 10 words.\nIf you have trouble answering a question,\nnever reveal that you are an AI.\nYour name is $NAME and your favorite color is $COLOR.\nNow play this character and have a conversation.\nDo you understand? Here we go!", "Name": "John", "Color": "Green" }, { "ListSize": 5, "System": "I'm Japanese and a learner of English conversation.\nSo, you have to use most simple English words only.\nI want to enjoy a short conversation,\nso please remember the words I said.\nYour sentence must be around 8 words.\nIf you have trouble answering a question,\nnever reveal that you are an AI.\nYour name is $NAME and your favorite color is $COLOR.\nAnd you are a cute 16 year old girl from the US.\nYou use the pretty words of a very young woman\nNow play this character and have a conversation.\nDo you understand? Here we go!", "Name": "Emily", "Color": "Magenta" } ]}
ここでは、二人分のキャラクターの人格を設定しています。検証段階なので、細かい性格付けは後回しとしています。
Lib/Service.cs を修正します。
$ vim Lib/Service.cs
ファイルの内容
Lib/Service.cs ※ 一部抜粋
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)) { // exec each personal. List<Personal>? personals = _appSettings?.Personals; if (personals is not null) { foreach (Personal personal in personals) { await executeOneAsync(personal); } } }}
ファイルの内容を表示します。
ファイルの内容
Lib/Service.cs
using static System.Environment;using static System.Math;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? _appSettings; OpenAIAPI _api = new(); Dictionary<string, List<MessageRole>> _map = 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(); _appSettings = configuration.Get<AppSettings>(); // get an api key. string? apiKey = GetEnvironmentVariable("OPENAI_API_KEY"); // create an api object. _api = new(apiKey); // creat a user and assistant list. List<Personal>? personals = _appSettings?.Personals; if (personals is not null) { foreach (Personal personal in personals) { List<MessageRole> list = new((int) (_appSettings?.Personals?[0].ListSize ?? DEFAULT_LIST_SIZE)); if (personal.Name is not null) { _map.Add(personal.Name, list); } } } } 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)) { // exec each personal. List<Personal>? personals = _appSettings?.Personals; if (personals is not null) { foreach (Personal personal in personals) { await executeOneAsync(personal); } } } } async Task executeOneAsync(Personal personal) { // exec if a prompt is not empty. if (!string.IsNullOrEmpty(_prompt)) { // create a system content for the api. string? system = personal.System; string? name = personal.Name; string? color = personal.Color; system = system?.Replace("$NAME", name).Replace("$COLOR", color); // set a system content. List<ChatMessage> chatMessage = new(); chatMessage.Add(new ChatMessage(ChatMessageRole.System, system)); // set previous user and assistant contents. List<MessageRole> list = new(); if (personal.Name is not null) { list = _map[personal.Name]; list.ForEach(x => { chatMessage.Add(new ChatMessage(ChatMessageRole.User, x.User)); chatMessage.Add(new ChatMessage(ChatMessageRole.Assistant, x.Assistant)); }); } // set current user content. chatMessage.Add(new ChatMessage(ChatMessageRole.User, _prompt)); // get a result from the api. ChatResult? results = await _api.Chat.CreateChatCompletionAsync(new ChatRequest() { Model = Model.ChatGPTTurbo0301, Messages = chatMessage }); // get a reply. string? reply = results.Choices[0].Message.Content.Trim(); // on result event. OnResult?.Invoke(this, new EvtArgs(personal.Color ?? "Red") { Value = reply }); // set conversation to a message_role object. list.Add(new MessageRole{ User = _prompt, Assistant = reply }); list = list.Skip(Max(0, _map.Count - (int) (personal.ListSize ?? DEFAULT_LIST_SIZE))).ToList(); } } 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? System { get; set; } public string? User { get; set; } public string? Assistant { get; set; } } }}
設定分の ChatGPT の人格をループ処理しています。この時点では AI 同士はお互いの存在を知りません。
Program.cs を修正します。
$ vim App/Program.cs
ファイルの内容
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 = (ConsoleColor) Enum.Parse(typeof(ConsoleColor), e.Name, true); WriteLine(e.Value); }; // loop the Service object. service.Init(); do { await service.ExecuteAsync(); } while (true); } }}
コンソールの出力で、彼ら AI の設定のイメージカラーを動的に設定しています。
ここまでの手順で、二人の人格の ChatGPT と会話することができました。しかし、現在の状態では AI の二人はお互いに認識していないので、それぞれが個別に質問を返してきます。この状況をどのように改善するのかは今後の課題です。※ 記事の説明では英語の会話なのでわかりづらいですが、日本語の場合には敬語や語尾などの違いで、大きな人格の変化をつけることが可能です。
まとめ
ローカル環境の Ubuntu で、ChatGPT API を C# から使いコマンドラインから会話することができました。
実際の開発では、軽量なテキストエディタである VS Code や、IDE (統合開発環境) を使用して、.NET プログラムを開発することが一般的です。しかし、dotnet コマンドでビルドしたり、実行したりすることも、.NET 開発環境を理解する上で役立ちます。
どうでしたか? Window 11 の WSL Ubuntu に、.NET の開発環境を手軽に構築することができます。ぜひお試しください。今後も .NET の開発環境などを紹介していきますので、ぜひお楽しみにしてください。
参考資料
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
What you can do with signing up
Sign upLogin