-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmessage.js
174 lines (149 loc) · 5.81 KB
/
message.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
require('dotenv').config();
const fs = require('fs');
const natural = require('natural');
const axios = require('axios');
const path = require('path');
const Handlebars = require('handlebars');
const maxContext = 1337
const maxOutput = 42 //Tokens to generate each call (we cut off extra text in chat)
const logLimit = 10 //How many lines of recent chat history to include in prompt
const headerLimit = 33 //Add more chat history above the character sheet
// Horde API key sent in HTTP POST header
const config = {
headers: {
apikey: process.env.HORDE_APIKEY,
'Content-Type': 'application/json',
}
}
// Export this function
async function sendMessage(message, intent) {
const authorName = message.author.username;
const userId = message.author.id;
const userDirectory = path.join('./user', userId);
const characterFile = path.join(userDirectory, 'character.json');
const chatLogFile = path.join(userDirectory, 'chatlog.txt');
// Read character data
let bot = {};
try {
bot = JSON.parse(fs.readFileSync(characterFile, 'utf8'));
//Set the user's name for Handlebars, keep out of objects passed to prompt
bot.user = authorName
} catch (error) {
console.error(`Failed to read character data for user ${userId}:`, error);
}
// Compile the Handlebars template to fill name/user into dialog sample
let bd = JSON.stringify(bot.dialog)
const template = Handlebars.compile(bd);
// Fill in the variables with data from the bot object
const botDialog = template(bot);
// Prepare profile facts for prompt
let facts = JSON.stringify(bot.fact)
// Clean up the output string
const botFacts = facts.replace(/["'\{\}\[\]]/g, '');
//EXPERIMENTAL
//Use 'intent answers' for memory or other notes to bot with dynamic profile vars.
if (intent) {
//Append intent-recognition note if attached (answer in corpus)
const logEntry = `${authorName}: ${message.content} ${intent}\n`;
fs.appendFileSync(chatLogFile, logEntry);
} else {
// Append message to chat log
const logEntry = `${authorName}: ${message.content}\n`;
fs.appendFileSync(chatLogFile, logEntry);
}
// Read updated chat log to post to API
let chatLogContent = '';
try {
chatLogContent = fs.readFileSync(chatLogFile, 'utf8').split('\n');
} catch (error) {
console.error(`Failed to read chat log for user ${userId}:`, error);
}
//Get log entries
const lastLogs = chatLogContent.slice(Math.max(chatLogContent.length - logLimit, 0)).join('\n');
const headerLogs = chatLogContent.slice(Math.max(chatLogContent.length - logLimit - headerLimit, 0), Math.max(chatLogContent.length - logLimit, 0)).join('\n');
//Build the prompt now!
//This can be fine-tuned... have fun!
let state = `[It is ${bot.time} in ${bot.location}, ${bot.name} is wearing ${bot.wearing} and feeling ${bot.mindset}.]`
let prompt = `${headerLogs}\n[${botFacts}]\n${botDialog}\n${state}\n${lastLogs}\n${bot.name}:`
console.log(prompt);
// Count the tokens (for dev/debug, not used in logic yet)
const tokenizer = new natural.WordTokenizer();
const tokens = tokenizer.tokenize(prompt);
const tokenCount = tokens.length;
console.log(tokenCount + " TOKENS IN PROMPT")
//Build the REST payload (kobold AI/horde settings)
const apiPayload = {
"prompt": prompt,
"params": {
"n": 1,
"max_context_length": maxContext,
"max_length": maxOutput,
"rep_pen": 1.1,
"temperature": 0.69,
"top_p": 0.5,
"top_k": 0,
"top_a": 0.75,
"typical": 0.19,
"tfs": 0.97,
"rep_pen_range": 1024,
"rep_pen_slope": 0.7,
"sampler_order": [6,5,4,3,2,1,0]
},
"models": [
"PygmalionAI/pygmalion-6b"
],
"workers": []
};
// POST to LLM API
let response = await axios.post(
'https://horde.koboldai.net/api/v2/generate/text/async',
apiPayload,
config);
let jobId = response.data.id;
console.log("HORDE: "+ response); //DEBUG ===========
// GET polling response API
let generations;
while (true) {
const jobStatus = await axios.get(`https://horde.koboldai.net/api/v2/generate/text/status/${jobId}`);
if (jobStatus.data.done) {
generations = jobStatus.data.generations || []; //NEW
//generations = jobStatus.data.generations;
console.log(generations); //DEBUG ===========
break;
}
console.log(JSON.stringify(jobStatus.data))
await new Promise(resolve => setTimeout(resolve, 1111)); // 1111ms polling
//Add fail counter and/or faulted check.
}
//////////////////////////////////////////////
// Extract bot response from returned payload
// This needs some work to not cut off our bot's reply too early
//let botResponse = generations[0].text;
let botResponse = '[Error in LLM call.]'; //Return "error" if value not overwritten, avoid undefined error
//why is this even giving an error sometimes?
if (generations && generations.length > 0) {
for (let i = 0; i < generations.length; i++) {
botResponse = `${generations[i].text}`;
console.log("FULL REPLY> "+`${generations[i].text}`); //DEBUG ===========
}
}
// Clean response, discarding extra dialog generated
//let cleanedString = botResponse.replace(/^\s+/, ''); // remove line breaks before words
let matchPattern = bot.user+':'; // variable name to match with colon at the end
let index = botResponse.indexOf(matchPattern); // find index of match pattern
if (index !== -1) {
botResponse = botResponse.substring(0, index); // return everything up to match pattern
} else {
botResponse = botResponse; // match pattern not found, return entire string
}
// Append bot response to chat log
let botLogEntry = `${bot.name}: ${botResponse}\n`;
try {
fs.appendFileSync(chatLogFile, botLogEntry);
//console.log(botLogEntry); //DEBUG ===========
} catch (err) {
console.error(`Error writing to chat log: ${err}`);
}
return botResponse;
}
module.exports = { sendMessage };