mm_os
Version:
MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。
525 lines (466 loc) • 12.9 kB
JavaScript
require('mm_expand');
const { Nlp } = require('@nlpjs/basic');
/**
* 自然语言处理类
*/
class NLP {
static config = {
languages: ['zh', 'en'],
language: 'zh',
dir: './nlp',
// 模型文件
model: './model.nlp',
// 正则表达式文件
regex: './regex.json'
};
/**
* 构造函数
* @param {Object} config - 配置参数
*/
constructor(config) {
this.config = {
...NLP.config,
...config
};
this._preset();
}
}
/**
* 获取模型文件路径
* @returns {string} 模型文件路径
*/
NLP.prototype.getModelFile = function () {
return this.config.model.fullname(this.config.dir);
};
/**
* 获取正则表达式文件路径
* @returns {string} 正则表达式文件路径
*/
NLP.prototype.getRegexFile = function () {
return this.config.regex.fullname(this.config.dir);
};
/**
* 预设
* @returns {string} 语言
*/
NLP.prototype._preset = function () {
console.log('[_init] 开始初始化NLP管理器');
console.log('[_init] 配置:', JSON.stringify(this.config, null, 2));
// 初始化NLP管理器
this.nlp = new Nlp(this.config);
console.log('[_init] NLP管理器创建成功');
this.load();
console.log('[_init] 初始化完成');
};
/**
* 加载模型
* @param {string} model_file - 模型文件路径
* @param {string} regex_file - 正则表达式文件路径
*/
NLP.prototype.load = function (model_file, regex_file) {
this.loadRegex(regex_file);
this.loadModel(model_file);
};
/**
* 加载模型
* @param {string} file - 模型文件路径
*/
NLP.prototype.loadModel = async function (file) {
let f = file || this.getModelFile();
console.log(`[loadModel] 加载模型文件: ${f}`);
let json = f.loadJson();
if (json) {
this.nlp.fromJSON(json);
}
};
/**
* 加载正则表达式
* @param {string} file - 正则表达式文件路径
*/
NLP.prototype.loadRegex = function (file) {
let f = file || this.getRegexFile();
console.log(`[loadRegex] 加载正则表达式文件: ${f}`);
this.regex = f.loadJson();
};
/**
* 保存模型
* @param {string} model_file - 模型文件路径
* @param {string} regex_file - 正则表达式文件路径
*/
NLP.prototype.save = function (model_file, regex_file) {
console.log(`[save] 开始保存模型...`);
// 保存模型
this.saveModel(model_file);
console.log(`[save] 模型保存成功`);
// 保存正则表达式
this.saveRegex(regex_file);
console.log(`[save] 正则表达式保存成功`);
};
/**
* 保存模型
* @param {string} file - 模型文件路径
*/
NLP.prototype.saveModel = function (file) {
let model = this.nlp.toJSON();
let f = file || this.getModelFile();
console.log(`[saveModel] 保存模型文件: ${f}`);
f.saveJson(model);
};
/**
* 保存正则表达式
* @param {string} file - 正则表达式文件路径
*/
NLP.prototype.saveRegex = function (file) {
let f = file || this.getRegexFile();
console.log(`[saveRegex] 保存正则表达式文件: ${f}`);
f.saveJson(this.regex);
};
/**
* 添加文档
* @param {string} doc - 文档
* @param {string} intent - 指令
*/
NLP.prototype.addDocument = function (doc, intent) {
console.log(`[addDocument] 添加文档: "${doc}" -> "${intent}"`);
console.log(`[addDocument] 语言配置: ${this.config.language}`);
try {
// 明确指定locale
this.nlp.addDocument(this.config.language, doc, intent);
console.log(`[addDocument] 文档添加完成`);
} catch (error) {
console.log(`[addDocument] 添加文档失败:`, error.message);
throw error;
}
};
/**
* 添加命名实体识别文本
* @param {string} name - 命名实体名称
* @param {string} value - 值
* @param {Array} langs - 语言数组
* @param {Array} values - 值数组
*/
NLP.prototype.addNamedEntityText = function (name, value, langs, values) {
this.nlp.addNamedEntityText(name, value, langs, values);
};
/**
* 处理文本
* @param {string} text - 输入文本
* @returns {Object} 响应对象
*/
NLP.prototype.process = async function (text) {
let res = await this.nlp.process(this.config.language, text);
return res;
};
/**
* 提取参数词汇
* @param {Object} params - 参数
* @returns {Array} 词汇数组
*/
NLP.prototype._extractWords = function (params) {
let words = [];
if (!Array.isArray(params)) {
return words;
}
for (let i = 0; i < params.length; i++) {
let param = params[i];
if (!param || !param.example) {
continue;
}
let arr = param.example.split(';');
for (let n = 0; n < arr.length; n++) {
let text = arr[n];
let values = text.split('|');
// 提取所有值,而不仅仅是第一个
for (let k = 0; k < values.length; k++) {
let value = values[k].trim();
if (value) {
words.push({
name: param.name,
value,
values
});
}
}
}
}
return words;
};
/**
* 训练文档
* @param {string} intent - 指令
* @param {Array} examples - 示例数组
* @param {Array} words - 词汇数组
*/
NLP.prototype._trainDocuments = function (intent, examples, words) {
if (!Array.isArray(examples)) {
return;
}
for (const tpl of examples) {
if (tpl.indexOf('](') === -1) {
let docs = this.toDocs(tpl, words);
for (const doc of docs) {
this.addDocument(doc, intent);
}
}
else {
this.addDocument(tpl, intent);
}
}
};
/**
* 训练命名实体识别模型
* @param {Array} words - 词汇数组
* @param {string} lang - 语言
*/
NLP.prototype._trainNamedEntities = function (words, lang) {
if (!Array.isArray(words)) {
return;
}
for (const word of words) {
let { name, value, values } = word;
this.addNamedEntityText(name, value, [lang], values);
}
};
/**
* 训练模型
* @param {string} intent - 指令,格式xxx_xxx
* @param {string} example - 示例组
* @param {Object} params - 参数
*/
NLP.prototype.train = async function (intent, example, params) {
console.log(`[train] 开始训练模型,意图: "${intent}"`);
console.log(`[train] 示例组: "${example}"`);
console.log(`[train] 参数:`, JSON.stringify(params, null, 2));
// 参数校验
if (typeof intent !== 'string') {
throw new TypeError('指令必须是字符串');
}
if (typeof example !== 'string') {
throw new TypeError('示例组必须是字符串');
}
if (!Array.isArray(params)) {
throw new TypeError('参数必须是数组');
}
try {
let lang = this.config.language;
let examples = example.split(';');
console.log(`[train] 拆分后的示例:`, examples);
let words = this._extractWords(params);
console.log(`[train] 提取的词汇:`, words);
this._trainDocuments(intent, examples, words);
console.log(`[train] 文档训练完成`);
this._trainNamedEntities(words, lang);
console.log(`[train] 命名实体训练完成`);
// 训练模型
console.log(`[train] 开始模型训练...`);
try {
await this.nlp.train({
trainByDomain: false,
useNeural: false,
log: false
});
console.log(`[train] 模型训练完成`);
} catch (error) {
// 如果训练过程中出现文件保存错误,继续执行
if (error.message.includes('File cannot be written in web')) {
console.log(`[train] 模型训练完成(文件保存错误已忽略)`);
} else {
throw error;
}
}
} catch (error) {
console.log(`[train] 训练失败:`, error.message);
console.log(`[train] 错误堆栈:`, error.stack);
// 异步方法返回相应类型值,这里返回undefined
return undefined;
}
};
/**
* 转为文档
* @param {string} tpl - 模板
* @param {Array} words - 词汇数组
*/
NLP.prototype.toDocs = function (tpl, words) {
let docs = [tpl]; // 先添加原始模板
// 将大白话转为训练文档,例如:“我想听邓紫棋的喜欢你”转换为“我想听[邓紫棋](singer)的[喜欢你](music_name)”
// 正确的逻辑:模板包含value字段的词,替换为values中的词加上标注
// 第一步:收集所有词汇的替换信息
let word_replacements = [];
for (let i = 0; i < words.length; i++) {
let word = words[i];
if (!word || !word.value) continue;
let { name, value, values } = word;
// 检查模板是否包含value字段的词
if (!tpl.includes(value)) {
continue;
}
let replacements = [];
// 处理每个词汇的values数组
for (let j = 0; j < values.length; j++) {
let value_str = values[j];
let value_arr = value_str.split('|');
for (let k = 0; k < value_arr.length; k++) {
let val = value_arr[k].trim();
if (val) {
replacements.push({
name: name,
original_value: value, // 模板中实际出现的词
replacement_value: val, // 要替换成的词
replacement: `[${val}](${name})`
});
}
}
}
if (replacements.length > 0) {
word_replacements.push(replacements);
}
}
// 第二步:生成所有词汇值的笛卡尔积组合
if (word_replacements.length > 0) {
let combinations = this._generateCartesianProduct(word_replacements);
for (let combination of combinations) {
let doc = tpl;
for (let replacement of combination) {
// 将模板中的original_value替换为replacement
doc = doc.replace(replacement.original_value, replacement.replacement);
}
if (!docs.includes(doc)) {
docs.push(doc);
}
}
}
return docs;
};
/**
* 生成笛卡尔积
* @param {Array} arrays - 输入数组的数组
* @returns {Array} 笛卡尔积结果
*/
NLP.prototype._generateCartesianProduct = function (arrays) {
if (arrays.length === 0) return [[]];
let result = [];
function cartesianHelper(arr, index, current) {
if (index === arrays.length) {
result.push([...current]);
return;
}
for (let i = 0; i < arrays[index].length; i++) {
current.push(arrays[index][i]);
cartesianHelper(arr, index + 1, current);
current.pop();
}
}
cartesianHelper(arrays, 0, []);
return result;
};
/**
* 生成正则表达式
* @returns {Object} 正则表达式对象
*/
NLP.prototype.genRegex = function (examples) {
// 生成正则表达式
let regex = {};
for (const example of examples) {
let res = this.process(example);
let intent = res.intent;
if (!regex[intent]) {
regex[intent] = [];
}
let rules = this.buildRule(example, res);
regex[intent].push(...rules);
}
return regex;
};
/**
* 构建正则表达式
* @param {string} text - 文本
* @param {Object} res - 处理结果
* @returns {array} 正则表达式数组
*/
NLP.prototype.buildRule = function (text, res) {
let rules = [];
for (const entity of res.entities) {
let regex =
rules.push({
// 正则表达式
regex,
// 参数名
props: []
});
}
return rules;
};
/**
* 获取正则表达式数组
* @returns {array} 正则表达式数组
*/
NLP.prototype._getRegexs = function () {
let regexs = ['[a-zA-Z0-9_]+', '[\\u4e00-\\u9fa5]+', '[\\s\\S]+', '\\d+', '\\w+', '\\S+', '(.*)'];
return regexs;
};
/**
* 获取符合的正则表达式
* @param {string} text - 文本
* @returns {array} 符合的正则表达式数组
*/
NLP.prototype._extractRegexs = function (text) {
let regexs = this._getRegexs();
let arr = [];
for (const re of regexs) {
let m = text.match(re);
if (m) {
arr.push(re);
}
}
return arr;
};
/**
* 通过正则表达式提取关键词
* @param {string} regex - 正则表达式
* @param {string} text - 文本
* @returns {array} 关键词数组
*/
NLP.prototype._extractKeywords = function (regex, text) {
let matches = text.match(regex);
return matches;
};
/**
* 聊天
* @param {string} text - 输入文本
* @returns {Object} 响应对象
*/
NLP.prototype.chat = async function (text) {
// 先尝试能否有输出
let res = await this.process(text);
// 如果没有尝试正则匹配
if (!res.intent) {
let matches = this._tryMatches(text);
}
return res;
};
/**
* 尝试匹配正则表达式
* @param {string} text - 文本
* @returns {array} 符合的正则表达式数组
*/
NLP.prototype._tryMatches = function (text) {
let dict = this.regex;
let intent = null;
let words = [];
for (const key of dict) {
let regexs = dict[key];
for (let i = 0; i < regexs.length; i++) {
let matches = this._extractKeywords(regexs[i], text);
if (matches) {
intent = key;
words.push(...matches);
break;
}
}
}
return {
intent,
words
};
};
module.exports = { NLP };