🤖
マイクラを学習するbotを作る!(1/n)
概要
目標
Minecraftのマルチサーバーでプレイヤーに教わりながら操作を学習するbotを作る。
方針
- botの作成、マルチサーバーへのログイン、実際の動作はMineflayerを使用。
- OpenAI APIのChatGPT4を使用してコードを生成、スキルライブラリに追加。
- 随時スキルライブラリからコードを呼び出して実行することでマイクラの操作を行わせる。
- 基本的な仕組みは先行研究であるNVIDIAのVoyagerを参考にする。
環境
- ローカル端末:MacbookPro 1.4GHz クアッドコアIntel Corei5 メモリ8GB
- レンタルサーバー:ConohaVPS メモリ8GB CentOS
今回
今回の目標
- Mineflayerを用いたbotの作成・マルチサーバーへのログイン
- OpenAI APIとの連携
- OpenAI APIのChatGPT4を使用してbotを話せる機能の追加
- その他標準搭載スキルの追加 ◀︎ここまで
今回の方針
- botを生成・ログインさせる
- チャット更新を検知して取得する
- 先頭の文字が「bot,」から始まればbotへのメッセージとして処理する
- メッセージを個別チャットの場合と全体チャットの場合で分けて処理する
- 「bot,」に続く文言で各スキルの呼び出しを行う
- どのスキルにも対応しない文言の場合、普通の会話とみなしてchatGPTに渡す
- 普通の会話の場合、以前の会話履歴もchatGPTに渡すことで文脈に沿った会話を実現する
とりあえずできたもの
ファイル構成
ファイル構成
.
├── prompts
│ └── chat.ts
├── data
│ ├── groupChatData
│ │ └── groupChatHistory.txt
│ └── privateChatData
├── common
│ ├── alwaysRun.ts
│ ├── doTask.ts
│ ├── getResponseFromGPT.ts
│ ├── groupMessage.ts
│ └── privateMessage.ts
├── task
│ ├── attack.ts
│ ├── checkSaturation.ts
│ ├── eat.ts
│ ├── face.ts
│ ├── follow.ts
│ ├── groupChat.ts
│ ├── privateChat.ts
│ ├── run.ts
│ ├── slot.ts
│ └── throw.ts
├── .env
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── bot.ts
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json
botの生成を行う
botの生成を行うコード
bot.ts
const mineflayer = require('mineflayer');
import dotenv from 'dotenv';
dotenv.config();
const bot = mineflayer.createBot({
host: process.env.HOST,
port: Number(process.env.PORT),
username: process.env.USER_NAME,
auth: 'microsoft',
password: process.env.PASSWORD,
});
bot.on('message', async (jsonMsg, position) => {
//省略
});
各タスクの呼び出しを行う
個別チャットか全体チャットの処理を呼び出す(bot.ts)
bot.ts
import { groupMessage } from './common/groupMessage';
import { privateMessage } from './common/privateMessage';
bot.on('message', async (jsonMsg, position) => {
const json = jsonMsg.json;
if (json.translate !== '<%s> %s' && json.translate !== '%s whispers to you: %s') return;
//全体チャットか個別チャットの形式ではない場合は即return
const username = json.with[0].text;
const message = json.with[1].text;
if (username === bot.username) return;
//チャット元が自分の場合は場合は即return
if (json.translate === '<%s> %s') {
groupMessage(bot, groupChatHistory, username, message, CHAT_SETTINGS);
}
if (json.translate === '%s whispers to you: %s') {
privateMessage(bot, privateChatHistory, username, message, CHAT_SETTINGS);
}
return;
});
全体チャットの場合の処理を行う(groupMessage.ts)
groupMessage.ts
import { doTask } from './doTask';
export const groupMessage = async (bot, chatHistory, username, chatContents, CHAT_SETTINGS) => {
if (chatContents.substring(0, 4) !== 'bot,') return;
const message = chatContents.substring(4);
if (message.startsWith('/code')) {
const task = message.slice(4); // '/code'の部分を除去
// ここでChatGPT APIを使ってコード生成の予定
} else {
doTask(bot, chatHistory, username, CHAT_SETTINGS, message);
}
};
個別チャットの場合の処理を行う(privateMessage.ts)
privateMessage.ts
import { privateChat } from '../task/privateChat';
export const privateMessage = async (bot, chatHistory, username, message, CHAT_SETTINGS) => {
if (message.startsWith('/code')) {
const task = message.slice(4); // '/code'の部分を除去
// ここでChatGPT APIを使ってコード生成の予定
} else {
doTask(bot, chatHistory, username, CHAT_SETTINGS, message);
}
};
各タスクの呼び出しを行う(doTask.ts)
doTask.ts
import { groupChat } from '../task/groupChat';
import { follow } from '../task/follow';
...
export const doTask = (bot, chatHistory, username, CHAT_SETTINGS, message) => {
if (message.startsWith('/follow')) {
follow(bot, username);
...
} else {
groupChat(bot, chatHistory, username, message, CHAT_SETTINGS);
}
};
chatGPTを呼び出す
会話履歴の処理を行う(groupChat.ts)
groupChat.ts
import { getResponseFromGPT } from '../common/getResponseFromGPT';
import fs from 'fs/promises';
import PROMPT from '../prompts/chat';
export const groupChat = async (bot, chatHistory, username, message, CHAT_SETTINGS) => {
// 新しいメッセージをチャット履歴に追加
chatHistory.push(`${username}: ${message}`);
const promptList: string[] = [];
promptList.push(...chatHistory);
promptList.push(PROMPT);
const prompt = promptList.join('\n');
const role = 'user';
const reply = await getResponseFromGPT(
role,
prompt,
CHAT_SETTINGS.TEMPERATURE,
CHAT_SETTINGS.MAX_TOKENS
);
// ボットの返答をチャット履歴に追加
chatHistory.push(`you: ${String(reply).replace(/\r?\n/g, ' ')}`);
while (chatHistory.length > CHAT_SETTINGS.MAX_LINES) {
chatHistory.shift();
}
bot.chat(reply);
console.log('message:\n', message);
console.log('reply:\n', reply);
const filePath = `Data/groupChatData/groupChatHistory.txt`;
const appendLine = async (filePath, line) => {
try {
await fs.appendFile(filePath, `\n${line}`);
} catch (err) {
console.error('Error appending file:', err);
}
};
appendLine(filePath, `${username}: ${message}`);
appendLine(filePath, `you: ${reply}`);
};
chatGPTとの送受信を行う(getResponseFromGPT.ts)
getResponseFromGPT.ts
import dotenv from 'dotenv';
import axios from 'axios';
dotenv.config();
const API_URL = process.env.API_URL ? process.env.API_URL : '';
const MODEL_NAME = process.env.MODEL_NAME;
const API_KEY = process.env.API_KEY;
export const getResponseFromGPT = async (role, prompt, TEMPERATURE, MAX_TOKENS) => {
const requestBody = {
messages: [{ role: role, content: prompt }],
model: MODEL_NAME,
temperature: TEMPERATURE,
max_tokens: MAX_TOKENS,
};
try {
const response = await axios.post(API_URL, requestBody, {
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + API_KEY,
},
});
const reply = response.data.choices[0].message.content;
return reply;
} catch (error) {
console.error(error);
}
};
Discussion