Fixed LLM bot support
This commit is contained in:
parent
792f12b925
commit
e1d059d72c
|
|
@ -1,17 +1,19 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { LLM } = require('@themaximalist/llm.js'); // or wherever your LLM package lives
|
const OpenAI = require('openai');
|
||||||
|
|
||||||
// read optional config file
|
// read optional config file
|
||||||
const configPath = path.join(__dirname, '..', 'llm_config.json');
|
const configPath = path.join(__dirname, 'llm_config.json');
|
||||||
const llmOptions = fs.existsSync(configPath)
|
const llmOptions = fs.existsSync(configPath)
|
||||||
? JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
? JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
console.log(`Read config from ${configPath}`);
|
||||||
|
|
||||||
class LLMBot {
|
class LLMBot {
|
||||||
constructor() {
|
constructor() {
|
||||||
// One instance keeps the whole conversation in memory
|
// Instantiate the OpenAI client with the config options we just read
|
||||||
this.llm = new LLM(llmOptions);
|
this.llm = new OpenAI(llmOptions);
|
||||||
|
|
||||||
// Prompt fragments we’ll reuse
|
// Prompt fragments we’ll reuse
|
||||||
this.writePrompt =
|
this.writePrompt =
|
||||||
|
|
@ -31,23 +33,39 @@ Keep it short, fun, and on-topic.`;
|
||||||
|
|
||||||
async Write(story_so_far) {
|
async Write(story_so_far) {
|
||||||
const msg = `${this.writePrompt}\n\nStory so far:\n${story_so_far}`;
|
const msg = `${this.writePrompt}\n\nStory so far:\n${story_so_far}`;
|
||||||
const llm_response = await this.llm.chat(msg);
|
const response = await this.llm.chat.completions.create({
|
||||||
console.log(`Received response from LLM: ${llm_response}`)
|
model: llmOptions.model || 'meta-llama/Llama-3.2-3B-Instruct-Turbo',
|
||||||
return llm_response.trim();
|
messages: [{ role: 'user', content: msg }],
|
||||||
|
temperature: llmOptions.temperature ?? 0.8,
|
||||||
|
max_tokens: llmOptions.max_tokens ?? 80,
|
||||||
|
});
|
||||||
|
console.log(`Received response from LLM: ${response.choices[0].message.content}`);
|
||||||
|
return response.choices[0].message.content.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Vote(story_so_far, choices) {
|
async Vote(story_so_far, choices) {
|
||||||
const choicesBlock = choices.map((c, i) => `${i}: ${c}`).join('\n');
|
const choicesBlock = choices.map((c, i) => `${i}: ${c}`).join('\n');
|
||||||
const msg = `${this.votePrompt}\n\nStory:\n${story_so_far}\n\nChoices:\n${choicesBlock}`;
|
const msg = `${this.votePrompt}\n\nStory:\n${story_so_far}\n\nChoices:\n${choicesBlock}`;
|
||||||
const reply = (await this.llm.chat(msg)).trim();
|
const response = await this.llm.chat.completions.create({
|
||||||
// extract first digit found, fallback to 0
|
model: llmOptions.model || 'meta-llama/Llama-3.2-3B-Instruct-Turbo',
|
||||||
|
messages: [{ role: 'user', content: msg }],
|
||||||
|
temperature: 0.2,
|
||||||
|
max_tokens: 5,
|
||||||
|
});
|
||||||
|
const reply = response.choices[0].message.content.trim();
|
||||||
const match = reply.match(/\d+/);
|
const match = reply.match(/\d+/);
|
||||||
return match ? parseInt(match[0], 10) : 0;
|
return match ? parseInt(match[0], 10) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Banter(chat_so_far) {
|
async Banter(chat_so_far) {
|
||||||
const msg = `${this.banterPrompt}\n\nChat so far:\n${chat_so_far}`;
|
const msg = `${this.banterPrompt}\n\nChat so far:\n${chat_so_far}`;
|
||||||
return (await this.llm.chat(msg)).trim();
|
const response = await this.llm.chat.completions.create({
|
||||||
|
model: llmOptions.model || 'meta-llama/Llama-3.2-3B-Instruct-Turbo',
|
||||||
|
messages: [{ role: 'user', content: msg }],
|
||||||
|
temperature: llmOptions.temperature ?? 0.8,
|
||||||
|
max_tokens: llmOptions.max_tokens ?? 60,
|
||||||
|
});
|
||||||
|
return response.choices[0].message.content.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
"openai": "^6.25.0",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
server.js
41
server.js
|
|
@ -117,7 +117,7 @@ class YarnGame {
|
||||||
console.log("Creating bots...")
|
console.log("Creating bots...")
|
||||||
for (let i = 0; i < botCount; i++) {
|
for (let i = 0; i < botCount; i++) {
|
||||||
const bot_id = randomUUID()
|
const bot_id = randomUUID()
|
||||||
const bot = BotFactory.createBot("random");
|
const bot = BotFactory.createBot("llm");
|
||||||
this.bots.push({ bot_id, bot });
|
this.bots.push({ bot_id, bot });
|
||||||
const bot_player = {
|
const bot_player = {
|
||||||
player_id: bot_id,
|
player_id: bot_id,
|
||||||
|
|
@ -130,23 +130,24 @@ class YarnGame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame(settings) {
|
async startGame(settings) {
|
||||||
this.gameSettings = { ...this.gameSettings, ...settings };
|
this.gameSettings = { ...this.gameSettings, ...settings };
|
||||||
this.startBots(this.gameSettings.botCount);
|
this.startBots(this.gameSettings.botCount);
|
||||||
this.inProgress = true;
|
this.inProgress = true;
|
||||||
this.yarnStory = [{ player: "Narrator", str: this.gameSettings.startText }];
|
this.yarnStory = [{ player: "Narrator", str: this.gameSettings.startText }];
|
||||||
this.players.forEach(p => p.score = 0);
|
this.players.forEach(p => p.score = 0);
|
||||||
this.startWritingPhase();
|
await this.startWritingPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
processBotWriting() {
|
async processBotWriting() {
|
||||||
for (const botObj of this.bots) {
|
for (const botObj of this.bots) {
|
||||||
const botText = botObj.bot.Write(this.getStoryText());
|
const botText = await botObj.bot.Write(this.getStoryText());
|
||||||
|
console.log(`Bot text: ${botText}`)
|
||||||
this.submitEntry(botObj.bot_id, botText)
|
this.submitEntry(botObj.bot_id, botText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startWritingPhase() {
|
async startWritingPhase() {
|
||||||
this.currentPhase = 'writing';
|
this.currentPhase = 'writing';
|
||||||
this.round_data = [];
|
this.round_data = [];
|
||||||
this.submittedPlayers = new Set();
|
this.submittedPlayers = new Set();
|
||||||
|
|
@ -176,18 +177,18 @@ class YarnGame {
|
||||||
return this.submittedPlayers.size >= this.players.length;
|
return this.submittedPlayers.size >= this.players.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
processBotVoting() {
|
async processBotVoting() {
|
||||||
for (const botObj of this.bots) {
|
for (const botObj of this.bots) {
|
||||||
const botVote = botObj.bot.Vote(this.getStoryText(), this.round_data);
|
const botVote = await botObj.bot.Vote(this.getStoryText(), this.round_data);
|
||||||
this.submitVote(botObj.bot_id, botVote)
|
this.submitVote(botObj.bot_id, botVote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startVotingPhase() {
|
async startVotingPhase() {
|
||||||
this.currentPhase = 'voting';
|
this.currentPhase = 'voting';
|
||||||
this.votedPlayers = new Set();
|
this.votedPlayers = new Set();
|
||||||
this.timeRemaining = 30; // 30 seconds for voting
|
this.timeRemaining = 30; // 30 seconds for voting
|
||||||
this.processBotVoting()
|
await this.processBotVoting()
|
||||||
}
|
}
|
||||||
|
|
||||||
submitVote(playerId, entryIndex) {
|
submitVote(playerId, entryIndex) {
|
||||||
|
|
@ -390,7 +391,7 @@ io.on('connection', (socket) => {
|
||||||
io.to(currentRoom).emit('settingsUpdate', game.gameSettings);
|
io.to(currentRoom).emit('settingsUpdate', game.gameSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('startGame', (settings) => {
|
socket.on('startGame', async (settings) => {
|
||||||
if (!currentRoom || !playerId) return;
|
if (!currentRoom || !playerId) return;
|
||||||
|
|
||||||
const game = games[currentRoom];
|
const game = games[currentRoom];
|
||||||
|
|
@ -401,13 +402,13 @@ io.on('connection', (socket) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Starting new game with settings ${JSON.stringify(settings)}`)
|
console.log(`Starting new game with settings ${JSON.stringify(settings)}`)
|
||||||
game.startGame(settings);
|
await game.startGame(settings);
|
||||||
io.to(currentRoom).emit('gameStarted', game.getState());
|
io.to(currentRoom).emit('gameStarted', game.getState());
|
||||||
|
|
||||||
startTimer(game);
|
await startTimer(game);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('submitEntry', (data) => {
|
socket.on('submitEntry', async (data) => {
|
||||||
if (!currentRoom || !playerId) return;
|
if (!currentRoom || !playerId) return;
|
||||||
|
|
||||||
const game = games[currentRoom];
|
const game = games[currentRoom];
|
||||||
|
|
@ -420,7 +421,7 @@ io.on('connection', (socket) => {
|
||||||
|
|
||||||
if (game.allPlayersSubmitted()) {
|
if (game.allPlayersSubmitted()) {
|
||||||
clearInterval(game.timer);
|
clearInterval(game.timer);
|
||||||
game.startVotingPhase();
|
await game.startVotingPhase();
|
||||||
io.to(currentRoom).emit('votingPhase', {
|
io.to(currentRoom).emit('votingPhase', {
|
||||||
entries: game.round_data.map((e, i) => ({
|
entries: game.round_data.map((e, i) => ({
|
||||||
index: i,
|
index: i,
|
||||||
|
|
@ -495,10 +496,10 @@ io.on('connection', (socket) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function startTimer(game) {
|
async function startTimer(game) {
|
||||||
if (game.timer) clearInterval(game.timer);
|
if (game.timer) clearInterval(game.timer);
|
||||||
|
|
||||||
game.timer = setInterval(() => {
|
game.timer = setInterval(async () => {
|
||||||
game.timeRemaining--;
|
game.timeRemaining--;
|
||||||
io.to(game.roomName).emit('timerUpdate', game.timeRemaining);
|
io.to(game.roomName).emit('timerUpdate', game.timeRemaining);
|
||||||
|
|
||||||
|
|
@ -512,7 +513,7 @@ function startTimer(game) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
game.startVotingPhase();
|
await game.startVotingPhase();
|
||||||
io.to(game.roomName).emit('votingPhase', {
|
io.to(game.roomName).emit('votingPhase', {
|
||||||
entries: game.round_data.map((e, i) => ({
|
entries: game.round_data.map((e, i) => ({
|
||||||
index: i,
|
index: i,
|
||||||
|
|
@ -558,10 +559,10 @@ function endVotingPhase(game) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Start next round after a short delay
|
// Start next round after a short delay
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
game.startWritingPhase();
|
game.startWritingPhase();
|
||||||
io.to(game.roomName).emit('newRound', game.getState());
|
io.to(game.roomName).emit('newRound', game.getState());
|
||||||
startTimer(game);
|
await startTimer(game);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue