From e1d059d72c4850e8e381fd43b349dc63d7e7ce9e Mon Sep 17 00:00:00 2001 From: cameron Date: Sun, 1 Mar 2026 09:59:26 -0500 Subject: [PATCH] Fixed LLM bot support --- bots/LLMBot.js | 38 ++++++++++++++++++++++++++++---------- package.json | 1 + server.js | 41 +++++++++++++++++++++-------------------- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/bots/LLMBot.js b/bots/LLMBot.js index 25aae9b..236c8d6 100644 --- a/bots/LLMBot.js +++ b/bots/LLMBot.js @@ -1,17 +1,19 @@ const fs = require('fs'); const path = require('path'); -const { LLM } = require('@themaximalist/llm.js'); // or wherever your LLM package lives +const OpenAI = require('openai'); // 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) ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : {}; +console.log(`Read config from ${configPath}`); + class LLMBot { constructor() { - // One instance keeps the whole conversation in memory - this.llm = new LLM(llmOptions); + // Instantiate the OpenAI client with the config options we just read + this.llm = new OpenAI(llmOptions); // Prompt fragments we’ll reuse this.writePrompt = @@ -31,23 +33,39 @@ Keep it short, fun, and on-topic.`; async Write(story_so_far) { const msg = `${this.writePrompt}\n\nStory so far:\n${story_so_far}`; - const llm_response = await this.llm.chat(msg); - console.log(`Received response from LLM: ${llm_response}`) - return llm_response.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 ?? 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) { 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 reply = (await this.llm.chat(msg)).trim(); - // extract first digit found, fallback to 0 + 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: 0.2, + max_tokens: 5, + }); + const reply = response.choices[0].message.content.trim(); const match = reply.match(/\d+/); return match ? parseInt(match[0], 10) : 0; } async Banter(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(); } } diff --git a/package.json b/package.json index e9e6175..586dc91 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "cookie-parser": "^1.4.7", "express": "^5.2.1", "node-fetch": "^3.3.2", + "openai": "^6.25.0", "socket.io": "^4.8.3", "uuid": "^13.0.0" } diff --git a/server.js b/server.js index 0915bdb..0c8a49c 100644 --- a/server.js +++ b/server.js @@ -117,7 +117,7 @@ class YarnGame { console.log("Creating bots...") for (let i = 0; i < botCount; i++) { const bot_id = randomUUID() - const bot = BotFactory.createBot("random"); + const bot = BotFactory.createBot("llm"); this.bots.push({ bot_id, bot }); const bot_player = { player_id: bot_id, @@ -130,23 +130,24 @@ class YarnGame { } } - startGame(settings) { + async startGame(settings) { this.gameSettings = { ...this.gameSettings, ...settings }; this.startBots(this.gameSettings.botCount); this.inProgress = true; this.yarnStory = [{ player: "Narrator", str: this.gameSettings.startText }]; this.players.forEach(p => p.score = 0); - this.startWritingPhase(); + await this.startWritingPhase(); } - processBotWriting() { + async processBotWriting() { 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) } } - startWritingPhase() { + async startWritingPhase() { this.currentPhase = 'writing'; this.round_data = []; this.submittedPlayers = new Set(); @@ -176,18 +177,18 @@ class YarnGame { return this.submittedPlayers.size >= this.players.length; } - processBotVoting() { + async processBotVoting() { 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) } } - startVotingPhase() { + async startVotingPhase() { this.currentPhase = 'voting'; this.votedPlayers = new Set(); this.timeRemaining = 30; // 30 seconds for voting - this.processBotVoting() + await this.processBotVoting() } submitVote(playerId, entryIndex) { @@ -390,7 +391,7 @@ io.on('connection', (socket) => { io.to(currentRoom).emit('settingsUpdate', game.gameSettings); }); - socket.on('startGame', (settings) => { + socket.on('startGame', async (settings) => { if (!currentRoom || !playerId) return; const game = games[currentRoom]; @@ -401,13 +402,13 @@ io.on('connection', (socket) => { } console.log(`Starting new game with settings ${JSON.stringify(settings)}`) - game.startGame(settings); + await game.startGame(settings); 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; const game = games[currentRoom]; @@ -420,7 +421,7 @@ io.on('connection', (socket) => { if (game.allPlayersSubmitted()) { clearInterval(game.timer); - game.startVotingPhase(); + await game.startVotingPhase(); io.to(currentRoom).emit('votingPhase', { entries: game.round_data.map((e, i) => ({ index: i, @@ -495,10 +496,10 @@ io.on('connection', (socket) => { }); }); -function startTimer(game) { +async function startTimer(game) { if (game.timer) clearInterval(game.timer); - game.timer = setInterval(() => { + game.timer = setInterval(async () => { 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', { entries: game.round_data.map((e, i) => ({ index: i, @@ -558,10 +559,10 @@ function endVotingPhase(game) { }); } else { // Start next round after a short delay - setTimeout(() => { + setTimeout(async () => { game.startWritingPhase(); io.to(game.roomName).emit('newRound', game.getState()); - startTimer(game); + await startTimer(game); }, 5000); } }