UNPKG

mm_os

Version:

MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。

525 lines (466 loc) 12.9 kB
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 };