var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config2, KnowledgeService: () => KnowledgeService, apply: () => apply7, inject: () => inject, logger: () => logger, name: () => name, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi3 = require("koishi"); var import_chat2 = require("koishi-plugin-chatluna/services/chat"); // src/plugins/command.ts var import_pagination = require("koishi-plugin-chatluna/utils/pagination"); var import_promises = __toESM(require("fs/promises"), 1); var import_path = __toESM(require("path"), 1); async function apply(ctx, config, plugin, chain4) { ctx.command("chatluna.knowledge", "ChatLuna 知识库相关命令"); ctx.command("chatluna.knowledge.upload ", "上传资料").option("size", "-s --size 文本块的切割大小(字符)").option("name", "-n --name 资料名称").option("overlap", "-o --overlap 文件路径").action(async ({ options, session }, path4) => { const loader = ctx.chatluna_knowledge.loader; const supported = await loader.support(path4); if (!supported) { return `不支持的文件类型:${path4}`; } const documents = await loader.load(path4, { chunkOverlap: options.overlap ?? config.chunkOverlap, chunkSize: options.size ?? config.chunkSize }); await session.send( `已对 ${path4} 解析成 ${documents.length} 个文档块。正在保存至数据库` ); await ctx.chatluna_knowledge.uploadDocument( documents, path4 // Bug? // options.name ); return `已成功上传到 ${ctx.chatluna.config.defaultVectorStore} 向量数据库`; }); ctx.command("chatluna.knowledge.init", "从默认文件夹初始化知识库").option("size", "-s --size 文本块的切割大小(字符)").option("overlap", "-o --overlap 文件路径").action(async ({ options, session }) => { const loader = ctx.chatluna_knowledge.loader; const load = /* @__PURE__ */ __name(async (path4) => { const supported = await loader.support(path4); if (!supported) { ctx.logger.warn(`不支持的文件类型:${path4}`); return false; } const documents = await loader.load(path4, { chunkOverlap: options.overlap ?? config.chunkOverlap, chunkSize: options.size ?? config.chunkSize }); ctx.logger.info( `已对 ${path4} 解析成 ${documents.length} 个文档块。正在保存至数据库` ); await ctx.chatluna_knowledge.uploadDocument(documents, path4); ctx.logger.info( `已成功上传到 ${ctx.chatluna.config.defaultVectorStore} 向量数据库` ); return true; }, "load"); const knowledgeDir = import_path.default.join( ctx.baseDir, "data/chathub/knowledge/default" ); const files = await import_promises.default.readdir(knowledgeDir); const successPaths = []; for (const file of files) { const filePath = import_path.default.join(knowledgeDir, file); const result = await load(filePath); if (result) { successPaths.push(filePath); } } return `已成功上传 ${successPaths.length} / ${files.length} 个文档到 ${ctx.chatluna.config.defaultVectorStore} 向量数据库`; }); ctx.command("chatluna.knowledge.delete [path:string]", "删除资料").option("db", "-d --db 数据库名").action(async ({ options, session }, path4) => { await session.send( `正在从数据库中删除 ${path4},是否确认删除?回复大写 Y 以确认删除` ); const promptResult = await session.prompt(1e3 * 30); if (promptResult == null || promptResult !== "Y") { return "已取消删除"; } await deleteDocument(ctx, path4, options.db); return `已成功删除文档 ${path4}`; }); ctx.command("chatluna.knowledge.list", "列出资料").option("page", "-p 页码", { fallback: 1 }).option("limit", "-l 每页数量", { fallback: 10 }).option("db", "-d --db 数据库名").action(async ({ options, session }) => { const pagination = new import_pagination.Pagination({ formatItem: /* @__PURE__ */ __name((value) => formatDocumentInfo(value), "formatItem"), formatString: { pages: "第 [page] / [total] 页", top: "以下是你目前所有已经上传的文档\n", bottom: "你可以使用 chatluna.knowledge.set 来切换当前环境里你使用的文档配置(文档配置不是文档)" } }); const documents = await ctx.chatluna_knowledge.listDocument( options.db ); await pagination.push(documents); return pagination.getFormattedPage(options.page, options.limit); }); } __name(apply, "apply"); function formatDocumentInfo(document) { return `${document.name} => ${document.path}`; } __name(formatDocumentInfo, "formatDocumentInfo"); async function deleteDocument(ctx, filePath, db) { await ctx.chatluna_knowledge.deleteDocument(filePath, db); } __name(deleteDocument, "deleteDocument"); // src/plugins/config.ts var import_koishi = require("koishi"); var import_types = require("koishi-plugin-chatluna/llm-core/platform/types"); async function apply2(ctx, config, plugin, chain4) { ctx.on("chatluna-knowledge/delete", (data) => { updateConfig(); }); ctx.on("chatluna-knowledge/upload", (data) => { updateConfig(); }); async function updateConfig() { const documents = await ctx.chatluna_knowledge.listDocument( ctx.chatluna.config.defaultVectorStore ); ctx.schema.set( "knowledge", import_koishi.Schema.union( documents.map((document) => import_koishi.Schema.const(document.name)).concat(import_koishi.Schema.const("无")) ) ); } __name(updateConfig, "updateConfig"); ctx.on("chatluna/model-added", (service) => { ctx.schema.set("model", import_koishi.Schema.union(getModelNames(service))); }); ctx.on("chatluna/model-removed", (service) => { ctx.schema.set("model", import_koishi.Schema.union(getModelNames(service))); }); ctx.schema.set("model", import_koishi.Schema.union(getModelNames(ctx.chatluna.platform))); updateConfig(); } __name(apply2, "apply"); function getModelNames(service) { return service.getAllModels(import_types.ModelType.llm).map((m) => import_koishi.Schema.const(m)); } __name(getModelNames, "getModelNames"); // src/llm-core/retrievers/multi_score_threshold.ts var import_vectorstores = require("@langchain/core/vectorstores"); var import_error = require("koishi-plugin-chatluna/utils/error"); var _MultiScoreThresholdRetriever = class _MultiScoreThresholdRetriever extends import_vectorstores.VectorStoreRetriever { constructor(input) { super({ vectorStore: input.vectorStores?.[0], k: input.maxK, filter: input.filter, searchType: input.searchType }); this.kIncrement = 5; this.maxK = 50; this.maxK = input.maxK ?? this.maxK; this.minSimilarityScore = input.minSimilarityScore ?? this.minSimilarityScore; this.kIncrement = input.kIncrement ?? this.kIncrement; this.vectorStores = input.vectorStores; } async getRelevantDocuments(query) { const currentKMap = /* @__PURE__ */ new Map(); const tempVectorResult = /* @__PURE__ */ new Map(); let currentIndex = 0; let vectorStore; let filteredResults = []; const currentMaxK = this.maxK * this.vectorStores.length / 2; let currentSearchK = 0; do { ; [vectorStore, currentIndex] = this.pickVectorStore(currentIndex); const vectorResult = tempVectorResult.get(vectorStore) ?? await vectorStore.embeddings.embedQuery(query); let currentK = currentKMap.get(vectorStore) ?? 0; currentK += this.kIncrement; currentSearchK += currentK; const results = await vectorStore.similaritySearchVectorWithScore( vectorResult, currentK, this.filter ); tempVectorResult.set(vectorStore, vectorResult); filteredResults = filteredResults.concat( results.filter(([, score]) => score >= this.minSimilarityScore) ); currentKMap.set(vectorStore, currentK); } while (filteredResults.length < currentMaxK && currentSearchK < currentMaxK); return filteredResults.map((documents) => documents[0]).slice(0, this.maxK); } pickVectorStore(index) { const vectorStore = this.vectorStores[index]; let nextIndex = index + 1; if (nextIndex >= this.vectorStores.length) { nextIndex = 0; } return [vectorStore, nextIndex]; } static fromVectorStores(vectorStores, options) { if (vectorStores.length < 1) { throw new import_error.ChatLunaError( import_error.ChatLunaErrorCode.KNOWLEDGE_VECTOR_NOT_FOUND ); } return new this({ ...options, vectorStores }); } }; __name(_MultiScoreThresholdRetriever, "MultiScoreThresholdRetriever"); var MultiScoreThresholdRetriever = _MultiScoreThresholdRetriever; // src/plugins/chat.ts var import_count_tokens = require("koishi-plugin-chatluna/llm-core/utils/count_tokens"); var import_error2 = require("koishi-plugin-chatluna/utils/error"); async function apply3(ctx, config, plugin, chain4) { const cache = /* @__PURE__ */ new Map(); ctx.on( "chatluna/before-chat", async (conversationId, message, promptVariables, chatInterface, chain5) => { if (chatInterface.chatMode === "plugin") { return void 0; } let searchChain = cache[conversationId]; if (!searchChain) { searchChain = await createSearchChain( ctx, config, chatInterface ); if (!searchChain) { return; } cache[conversationId] = searchChain; } const documents = await searchChain( message.content, await chatInterface.chatHistory.getMessages() ); logger.debug(`Documents: ${documents}`); promptVariables["knowledge"] = documents; } ); ctx.on("chatluna/clear-chat-history", async () => { cache.clear(); }); } __name(apply3, "apply"); async function createSearchChain(ctx, config, chatInterface) { const preset = await chatInterface.preset; const searchKnowledge = preset.knowledge?.knowledge; const chatVectorStore = ctx.chatluna.config.defaultVectorStore; const selectedKnowledge = []; if (searchKnowledge) { const regex = typeof searchKnowledge === "string" ? searchKnowledge : searchKnowledge.join("|"); const knowledge = await ctx.database.get("chathub_knowledge", { name: { $regex: new RegExp(regex) }, vector_storage: chatVectorStore }); selectedKnowledge.push(...knowledge); } else { const knowledge = await ctx.database.get("chathub_knowledge", { name: config.defaultKnowledge }); selectedKnowledge.push(...knowledge); } if (selectedKnowledge.length === 0) { logger.warn("No knowledge selected"); return; } else { logger.debug(`Selected knowledge: ${JSON.stringify(selectedKnowledge)}`); } const vectorStores = await Promise.all( selectedKnowledge.map( (knowledge) => ctx.chatluna_knowledge.loadVectorStore(knowledge.path) ) ); const retriever = createRetriever(ctx, config, vectorStores); if (!config.model) { throw new import_error2.ChatLunaError( import_error2.ChatLunaErrorCode.KNOWLEDGE_CONFIG_INVALID, new Error("model is not set") ); } const [platform, modelName] = (0, import_count_tokens.parseRawModelName)(config?.model); const model = await ctx.chatluna.createChatModel(platform, modelName).then((model2) => model2); return ctx.chatluna_knowledge.chains[config.mode](model, retriever); } __name(createSearchChain, "createSearchChain"); function createRetriever(ctx, config, vectorStores) { return MultiScoreThresholdRetriever.fromVectorStores(vectorStores, { minSimilarityScore: config.minSimilarityScore }); } __name(createRetriever, "createRetriever"); // src/plugin.ts async function plugins(ctx, plugin, config) { const chain4 = ctx.chatluna.chatChain; const plugins2 = ( // plugin start [apply, apply2, apply3] ); for (const apply8 of plugins2) { await apply8(ctx, config, plugin, chain4); } } __name(plugins, "plugins"); // src/service/knowledge.ts var import_koishi2 = require("koishi"); var import_count_tokens2 = require("koishi-plugin-chatluna/llm-core/utils/count_tokens"); var import_error4 = require("koishi-plugin-chatluna/utils/error"); // src/llm-core/document_loader/types.ts var _DocumentLoader = class _DocumentLoader { constructor(ctx, config, parent) { this.ctx = ctx; this.config = config; this.parent = parent; } }; __name(_DocumentLoader, "DocumentLoader"); var DocumentLoader = _DocumentLoader; // src/llm-core/document_loader/index.ts var import_error3 = require("koishi-plugin-chatluna/utils/error"); var import_text_splitter = require("langchain/text_splitter"); // src/llm-core/document_loader/loaders/json.ts var import_json = require("langchain/document_loaders/fs/json"); var _JsonLoader = class _JsonLoader extends DocumentLoader { load(path4, fields) { const loader = new import_json.JSONLoader(path4); return loader.load(); } async support(path4) { const ext = path4.split(".").pop() || ""; return ext.toLowerCase() === "json"; } }; __name(_JsonLoader, "JsonLoader"); var JsonLoader = _JsonLoader; // src/llm-core/document_loader/loaders/text.ts var import_text = require("langchain/document_loaders/fs/text"); var _TextDocumentLoader = class _TextDocumentLoader extends DocumentLoader { load(path4, fields) { const loader = new import_text.TextLoader(path4); return loader.load(); } async support(path4) { const ext = path4.split(".").pop() || ""; switch (ext.toLowerCase()) { case "text": case "txt": case "bat": case "java": case "js": case "ts": case "kt": case "lua": case "md": return true; default: return false; } } }; __name(_TextDocumentLoader, "TextDocumentLoader"); var TextDocumentLoader = _TextDocumentLoader; // src/llm-core/document_loader/loaders/csv.ts var import_csv = require("@langchain/community/document_loaders/fs/csv"); var _CSVDocumentLoader = class _CSVDocumentLoader extends DocumentLoader { load(path4, fields) { const loader = new import_csv.CSVLoader(path4); return loader.load(); } async support(path4) { const ext = path4.split(".").pop() || ""; return ext.toLowerCase() === "csv"; } }; __name(_CSVDocumentLoader, "CSVDocumentLoader"); var CSVDocumentLoader = _CSVDocumentLoader; // src/llm-core/document_loader/loaders/directory.ts var import_promises2 = __toESM(require("fs/promises"), 1); var import_path2 = __toESM(require("path"), 1); var _DirectoryLoader = class _DirectoryLoader extends DocumentLoader { async load(filePath, fields) { const fileList = (await import_promises2.default.readdir(filePath, { withFileTypes: true, recursive: true })).filter((value) => value.isFile()).map((value) => import_path2.default.join(value.parentPath, value.name)); let result = []; for (const subPath of fileList) { logger.debug(`parse document ${subPath}`); const subDocuments = await this.parent?.load(subPath, fields); if (!subDocuments) { continue; } result = result.concat(subDocuments); } return result; } async support(path4) { try { const stat = await import_promises2.default.stat(path4); return stat.isDirectory(); } catch (e) { return false; } } }; __name(_DirectoryLoader, "DirectoryLoader"); var DirectoryLoader = _DirectoryLoader; // src/llm-core/document_loader/loaders/doc.ts var import_docx = require("@langchain/community/document_loaders/fs/docx"); var _DocXDocumentLoader = class _DocXDocumentLoader extends DocumentLoader { load(path4, fields) { const loader = new import_docx.DocxLoader(path4); return loader.load(); } async support(path4) { const ext = path4.split(".").pop() || ""; return ext.toLowerCase() === "docx"; } }; __name(_DocXDocumentLoader, "DocXDocumentLoader"); var DocXDocumentLoader = _DocXDocumentLoader; // src/llm-core/document_loader/loaders/unstructured.ts var import_unstructured = require("@langchain/community/document_loaders/fs/unstructured"); var _UnstructuredDocumentLoader = class _UnstructuredDocumentLoader extends DocumentLoader { load(path4, fields) { const loader = new import_unstructured.UnstructuredLoader(path4, { apiKey: this.config.unstructuredApiKey, apiUrl: this.config.unstructuredApiEndpoint }); return loader.load(); } async support(path4) { const ext = path4.split(".").pop() || ""; const supportExt = [ "jpg", "jpeg", "png", "gif", "bmp", "pdf", "docx", "ppt", "epub", "xlsx", "xls", "pptx", "doc", "rtf" ]; return supportExt.indexOf(ext.toLowerCase()) !== -1; } }; __name(_UnstructuredDocumentLoader, "UnstructuredDocumentLoader"); var UnstructuredDocumentLoader = _UnstructuredDocumentLoader; // src/llm-core/document_loader/loaders/pdf.ts var import_pdf = require("@langchain/community/document_loaders/fs/pdf"); var _PDFDocumentLoader = class _PDFDocumentLoader extends DocumentLoader { load(path4, fields) { const loader = new import_pdf.PDFLoader(path4); return loader.load(); } async support(path4) { const ext = path4.split(".").pop() || ""; return ext.toLowerCase() === "pdf"; } }; __name(_PDFDocumentLoader, "PDFDocumentLoader"); var PDFDocumentLoader = _PDFDocumentLoader; // src/llm-core/document_loader/loaders/web.ts var import_cheerio = require("@langchain/community/document_loaders/web/cheerio"); var _WebLoader = class _WebLoader extends DocumentLoader { load(path4, fields) { const loader = new import_cheerio.CheerioWebBaseLoader(path4, { timeout: 1e3 * 60 * 2 }); return loader.load(); } async support(path4) { const supported = /^(http|https):\/\//i.test(path4); return supported; } }; __name(_WebLoader, "WebLoader"); var WebLoader = _WebLoader; // src/llm-core/document_loader/index.ts var _DefaultDocumentLoader = class _DefaultDocumentLoader extends DocumentLoader { constructor(ctx, config) { super(ctx, config, null); this._loaders = []; this._supportLoaders = {}; setInterval(() => { this._supportLoaders = {}; }, 1e3 * 60); } // eslint-disable-next-line @typescript-eslint/no-explicit-any async load(path4, fields) { const loader = await this._getLoader(path4); const documents = await loader.load(path4, fields); const textSplitter = new import_text_splitter.RecursiveCharacterTextSplitter({ chunkSize: fields.chunkSize ?? 1e3, chunkOverlap: fields.chunkOverlap ?? 100 }); return await textSplitter.splitDocuments(documents); } async support(path4) { for (const loader of this._loaders) { if (await loader.support(path4)) { this._supportLoaders[path4] = loader; return true; } } return false; } async _getLoader(path4) { let loader = this._supportLoaders[path4]; if (loader) { return loader; } const supported = await this.support(path4); if (!supported) { throw new import_error3.ChatLunaError( import_error3.ChatLunaErrorCode.KNOWLEDGE_UNSUPPORTED_FILE_TYPE, new Error(`Unsupported file type: ${path4}`) ); } loader = this._supportLoaders[path4]; return loader; } async init() { const loaders = [ (ctx, config, parent) => new JsonLoader(ctx, config, parent), (ctx, config, parent) => new TextDocumentLoader(ctx, config, parent), (ctx, config, parent) => new CSVDocumentLoader(ctx, config, parent), (ctx, config, parent) => new DirectoryLoader(ctx, config, parent), (ctx, config, parent) => new DocXDocumentLoader(ctx, config, parent), (ctx, config, parent) => new PDFDocumentLoader(ctx, config, parent), (ctx, config, parent) => new WebLoader(ctx, config, parent) ]; if (this.config.unstructuredApiKey?.length > 0) { loaders.push( (ctx, config, parent) => new UnstructuredDocumentLoader(ctx, config, parent) ); } for (const loader of loaders) { this._loaders.push(loader(this.ctx, this.config, this)); } } }; __name(_DefaultDocumentLoader, "DefaultDocumentLoader"); var DefaultDocumentLoader = _DefaultDocumentLoader; // src/service/knowledge.ts var import_base = require("koishi-plugin-chatluna/llm-core/model/base"); var import_crypto = require("crypto"); var import_path3 = __toESM(require("path"), 1); var import_promises3 = __toESM(require("fs/promises"), 1); var _KnowledgeService = class _KnowledgeService extends import_koishi2.Service { constructor(ctx, config) { super(ctx, "chatluna_knowledge"); this.ctx = ctx; this.config = config; this._vectorStores = {}; this._chains = {}; defineDatabase(ctx); const knowledgeDir = import_path3.default.join( ctx.baseDir, "data/chathub/knowledge/default" ); ctx.on("dispose", async () => { this._vectorStores = {}; }); ctx.on("ready", async () => { try { await import_promises3.default.access(knowledgeDir); } catch (error) { await import_promises3.default.mkdir(knowledgeDir, { recursive: true }); } }); this._loader = new DefaultDocumentLoader(ctx, config); } async createVectorStore(documentConfig) { const { path: path4, id, vector_storage: vectorStorage, embeddings: embeddingsName } = documentConfig; if (this._vectorStores[path4]) { return this._vectorStores[path4]; } const embeddings = await this.ctx.chatluna.createEmbeddings( ...(0, import_count_tokens2.parseRawModelName)(embeddingsName) ); const vectorStore = await this.ctx.chatluna.platform.createVectorStore( vectorStorage, { key: id, embeddings } ); this._vectorStores[path4] = vectorStore; return vectorStore; } async loadVectorStore(path4) { if (this._vectorStores[path4]) { return this._vectorStores[path4]; } const config = await this._getDocumentConfig(path4); if (!config) { throw new import_error4.ChatLunaError( import_error4.ChatLunaErrorCode.KNOWLEDGE_CONFIG_INVALID, new Error(`Knowledge config ${path4} not found`) ); } const vectorStore = await this.createVectorStore(config); return vectorStore; } async _getDocumentConfig(path4, vectorStore) { let selection = this.ctx.database.select("chathub_knowledge").where({ path: path4 }); if (vectorStore) { selection = selection.where({ vector_storage: vectorStore }); } let result = await selection.execute(); if (result.length === 0) { result = await this.ctx.database.select("chathub_knowledge").where({ path: path4 }).execute(); } return result[0]; } async deleteDocument(path4, db) { const config = await this._getDocumentConfig( path4, db ?? this.ctx.chatluna.config.defaultVectorStore ); if (config == null) { throw new import_error4.ChatLunaError( import_error4.ChatLunaErrorCode.KNOWLEDGE_VECTOR_NOT_FOUND, new Error(`Knowledge vector ${path4} from ${db} not found`) ); } const vectorStore = await this.createVectorStore(config); await vectorStore.delete({ deleteAll: true }); await this.ctx.database.remove("chathub_knowledge", { path: path4 }); delete this._vectorStores[config.path]; this.ctx.emit("chatluna-knowledge/delete", path4); } async listDocument(db) { let selection = this.ctx.database.select("chathub_knowledge"); if (db != null) { selection = selection.where({ vector_storage: db }); } const result = await selection.execute(); return result; } async uploadDocument(documents, filePath, name2) { const existsDocument = await this.ctx.database.get( "chathub_knowledge", { path: filePath } ); if (existsDocument.length > 0) { return; } const id = (0, import_crypto.randomUUID)(); name2 = name2 ?? this.extractNameFromPath(filePath); const config = { path: filePath, id, name: name2, vector_storage: this.ctx.chatluna.config.defaultVectorStore, embeddings: this.ctx.chatluna.config.defaultEmbeddings }; const vectorStore = await this.createVectorStore(config); const chunkDocuments = chunkArray(documents, 60); for (const chunk of chunkDocuments) { await vectorStore.addDocuments(chunk); } if (vectorStore instanceof import_base.ChatLunaSaveableVectorStore) { await vectorStore.save(); } this.ctx.database.upsert("chathub_knowledge", [config]); this.ctx.emit("chatluna-knowledge/upload", documents, filePath); } extractNameFromPath(filePath) { const cleanPath = filePath.replace(/^(https?:\/\/)/, ""); const parts = cleanPath.split(/[/\\]/); return parts[parts.length - 1] || "unknown"; } getChain(type) { return this._chains[type]; } get loader() { return this._loader; } get chains() { return this._chains; } createRetriever(vectorStores) { return MultiScoreThresholdRetriever.fromVectorStores(vectorStores, { minSimilarityScore: this.config.minSimilarityScore }); } }; __name(_KnowledgeService, "KnowledgeService"); _KnowledgeService.inject = ["database"]; var KnowledgeService = _KnowledgeService; function defineDatabase(ctx) { ctx.database.extend( "chathub_knowledge", { path: { type: "string", length: 254 }, id: { type: "string", length: 254 }, vector_storage: { type: "string", length: 254 }, embeddings: { type: "string", length: 254 }, name: { type: "string", length: 254 } }, { autoInc: false, primary: ["path", "name"] } ); } __name(defineDatabase, "defineDatabase"); function chunkArray(array, chunkSize) { const result = []; let startIndex = 0; try { while (startIndex < array.length) { const endIndex = Math.min(startIndex + chunkSize, array.length); result.push(array.slice(startIndex, endIndex)); startIndex += chunkSize; } } catch (error) { console.error("An error occurred:", error); } return result; } __name(chunkArray, "chunkArray"); // src/index.ts var import_logger = require("koishi-plugin-chatluna/utils/logger"); // src/llm-core/chains/fast.ts function apply4(ctx, chains2) { chains2["default"] = chain; } __name(apply4, "apply"); function chain(llm, baseRetriever) { return (query, chatHistory) => baseRetriever.invoke(query); } __name(chain, "chain"); // src/llm-core/chains/regenerate.ts var import_prompts = require("@langchain/core/prompts"); var question_generator_template = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question. Chat History: {chat_history} Follow Up Input: {question} (The original question needs to be output in the language of the question, and if the question is in Chinese, the question should also be generated in Chinese) Standalone question:`; function cropMessages(message) { return message.slice(-20).map((chatMessage) => { if (chatMessage._getType() === "human") { return `Human: ${chatMessage.content}`; } else if (chatMessage._getType() === "ai") { return `Assistant: ${chatMessage.content}`; } else if (chatMessage._getType() === "system") { return `System: ${chatMessage.content}`; } else { return `${chatMessage.content}`; } }).join("\n"); } __name(cropMessages, "cropMessages"); function chain2(llm, baseRetriever) { const questionGeneratorChainPrompt = import_prompts.PromptTemplate.fromTemplate( question_generator_template ); const questionGeneratorChain = questionGeneratorChainPrompt.pipe(llm); return async (query, chatHistory) => { if (chatHistory.length > 0) { const newQuestion = await questionGeneratorChain.invoke({ question: query, chat_history: cropMessages(chatHistory) }); const content = newQuestion.content; if (content.length > 0) { query = content; } } return await baseRetriever.invoke(query); }; } __name(chain2, "chain"); function apply5(ctx, chains2) { chains2["regenerate"] = chain2; } __name(apply5, "apply"); // src/llm-core/chains/contextual-compression.ts var import_contextual_compression = require("langchain/retrievers/contextual_compression"); var import_prompts2 = require("@langchain/core/prompts"); var import_chain_extract = require("langchain/retrievers/document_compressors/chain_extract"); var import_output_parsers = require("@langchain/core/output_parsers"); function getDefaultChainPrompt() { const outputParser = new NoOutputParser(); const template = PROMPT_TEMPLATE(outputParser.noOutputStr); return new import_prompts2.PromptTemplate({ template, inputVariables: ["question", "context"], outputParser }); } __name(getDefaultChainPrompt, "getDefaultChainPrompt"); var _NoOutputParser = class _NoOutputParser extends import_output_parsers.BaseOutputParser { constructor() { super(...arguments); // eslint-disable-next-line @typescript-eslint/naming-convention this.lc_namespace = [ "@langchain/core/messages", "retrievers", "document_compressors", "chain_extract" ]; this.noOutputStr = "NO_OUTPUT"; } parse(text) { const cleanedText = text.trim(); if (cleanedText === this.noOutputStr) { return Promise.resolve(""); } return Promise.resolve(cleanedText); } getFormatInstructions() { throw new Error("Method not implemented."); } }; __name(_NoOutputParser, "NoOutputParser"); var NoOutputParser = _NoOutputParser; var PROMPT_TEMPLATE = /* @__PURE__ */ __name((noOutputStr) => `Given the following question and context, extract any part of the context *AS IS* that is relevant to answer the question. If none of the context is relevant return ${noOutputStr}. Remember, *DO NOT* edit the extracted parts of the context, and output in the origin language of the part. > Question: {question} > Context: >>> {context} >>> Extracted relevant parts:`, "PROMPT_TEMPLATE"); function apply6(ctx, chains2) { chains2["contextual-compression"] = chain3; } __name(apply6, "apply"); function chain3(llm, baseRetriever) { const baseCompressor = import_chain_extract.LLMChainExtractor.fromLLM( llm, getDefaultChainPrompt() ); const retriever = new import_contextual_compression.ContextualCompressionRetriever({ baseCompressor, baseRetriever }); return (query, chatHistory) => retriever.invoke(query); } __name(chain3, "chain"); // src/chains.ts async function chains(ctx, plugin, config) { const plugins2 = [ apply4, apply5, apply6 ]; for (const apply8 of plugins2) { await apply8(ctx, ctx.chatluna_knowledge.chains); } } __name(chains, "chains"); // src/index.ts var logger; function apply7(ctx, config) { const plugin = new import_chat2.ChatLunaPlugin(ctx, config, "knowledge-chat"); logger = (0, import_logger.createLogger)(ctx, "chatluna-knowledge-chat"); ctx.on("ready", async () => { plugin.registerToService(); ctx.plugin(KnowledgeService, config); }); const pluginEntryPoint = /* @__PURE__ */ __name(async (ctx2) => { await ctx2.chatluna_knowledge.loader.init(); await plugins(ctx2, plugin, config); await chains(ctx2, plugin, config); }, "pluginEntryPoint"); ctx.plugin( { apply: /* @__PURE__ */ __name(async (ctx2, config2) => { ctx2.on("ready", async () => { await pluginEntryPoint(ctx2); }); }, "apply"), inject: ["chatluna", "chatluna_knowledge", "database"], name: "chatluna_knowledge_entry_point" }, config ); } __name(apply7, "apply"); var name = "chatluna-knowledge-chat"; var Config2 = import_koishi3.Schema.intersect([ import_koishi3.Schema.object({ defaultKnowledge: import_koishi3.Schema.dynamic("knowledge").description("默认的知识库 ID").default("无"), model: import_koishi3.Schema.dynamic("model").description("运行知识库的模型"), chunkSize: import_koishi3.Schema.number().default(500).max(2e3).min(10).description("文本块的切割大小(字符)"), chunkOverlap: import_koishi3.Schema.number().default(0).max(200).min(0).description( "文本块之间的最大重叠量(字体)。保留一些重叠可以保持文本块之间的连续性" ), mode: import_koishi3.Schema.union([ import_koishi3.Schema.const("default").description("直接对问题查询"), import_koishi3.Schema.const("regenerate").description("重新生成问题查询"), import_koishi3.Schema.const("contextual-compression").description("上下文压缩查询") ]).default("default").description("知识库运行模式"), minSimilarityScore: import_koishi3.Schema.number().role("slider").min(0).max(1).step(1e-3).default(0.5).description("文本搜索的最小相似度") }).description("基础配置"), import_koishi3.Schema.object({ unstructuredApiEndpoint: import_koishi3.Schema.string().role("url").default("http://127.0.0.1:8000").description("unstructured 接口地址"), unstructuredApiKey: import_koishi3.Schema.string().role("secret").description("unstructured 接口密钥") }).description("unstructured 配置") ]); var inject = ["chatluna"]; var usage = ` 现我们不再直接依赖相关库,你需要自己安装相关依赖到 koishi 根目录下。 要查看如何配置 pdf 文件, 看[这里](https://js.langchain.com/docs/modules/data_connection/document_loaders/how_to/pdf) 要查看如何配置 csv,看[这里](https://js.langchain.com/docs/modules/data_connection/document_loaders/integrations/file_loaders/csv) 要查看如何配置 docx,看[这里](https://js.langchain.com/docs/modules/data_connection/document_loaders/integrations/file_loaders/docx) 要查看如何配置 web 网页,看[这里](https://js.langchain.com/docs/modules/data_connection/document_loaders/integrations/web_loaders/web_cheerio) 使用之前用 \`chatluna knowledge upload\` 上传文件到知识库。然后在上面选择那个知识库。 先选择一次无,在选择这个知识库!!! 模型也要选择,随便选一个便宜的模型就行了。 `; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, KnowledgeService, apply, inject, logger, name, usage });