feat: chaite 初始化逻辑

This commit is contained in:
ikechan8370 2025-03-10 23:08:23 +08:00
parent 88312cdf38
commit fbcf4e6c08
16 changed files with 549 additions and 1269 deletions

View file

@ -1,9 +1,15 @@
class ChatGPTConfig { class ChatGPTConfig {
dataDir = 'data' dataDir = 'data'
processorsDirPath = 'data/processors' processorsDirPath = 'utils/processors'
toolsDirPath = 'data/tools' toolsDirPath = 'utils/tools'
cloudBaseUrl = '' cloudBaseUrl = ''
cloudApiKey = '' cloudApiKey = ''
embeddingModel = 'gemini-embedding-exp-03-07'
dimensions = 0
serverAuthKey = ''
version = '3.0.0'
} }
export default new ChatGPTConfig() export default new ChatGPTConfig()

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
import fs from 'node:fs' import fs from 'node:fs'
import { Config } from './utils/config.js' import ChatGPTConfig from './config/config.js'
import { createServer, runServer } from './server/index.js' import { initChaite } from './models/chaite/cloud.js'
logger.info('**************************************') logger.info('**************************************')
logger.info('chatgpt-plugin加载中') logger.info('chatgpt-plugin加载中')
@ -36,17 +35,9 @@ for (let i in files) {
global.chatgpt = { global.chatgpt = {
} }
// 启动服务器 initChaite()
if (Config.enableToolbox) {
logger.info('开启工具箱配置项,工具箱启动中')
await createServer()
await runServer()
logger.info('工具箱启动成功')
} else {
logger.info('提示当前配置未开启chatgpt工具箱可通过锅巴或`#chatgpt开启工具箱`指令开启')
}
logger.info('chatgpt-plugin加载成功') logger.info('chatgpt-plugin加载成功')
logger.info(`当前版本${Config.version}`) logger.info(`当前版本${ChatGPTConfig.version}`)
logger.info('仓库地址 https://github.com/ikechan8370/chatgpt-plugin') logger.info('仓库地址 https://github.com/ikechan8370/chatgpt-plugin')
logger.info('文档地址 https://www.yunzai.chat') logger.info('文档地址 https://www.yunzai.chat')
logger.info('插件群号 559567232') logger.info('插件群号 559567232')

View file

@ -1,18 +1,13 @@
import ChatGPTStorage from '../storage.js' import ChatGPTStorage from '../storage.js'
import { ChaiteStorage } from 'chaite'
/** class LowDBChannelStorage extends ChaiteStorage {
* @returns {import('chaite').ChannelsStorage}
*/
export async function createChannelsStorage () {
return new LowDBChannelStorage(ChatGPTStorage)
}
class LowDBChannelStorage {
/** /**
* *
* @param { LowDBStorage } storage * @param { LowDBStorage } storage
*/ */
constructor (storage) { constructor (storage = ChatGPTStorage) {
super()
this.storage = storage this.storage = storage
/** /**
* 集合 * 集合
@ -21,54 +16,50 @@ class LowDBChannelStorage {
this.collection = this.storage.collection('channel') this.collection = this.storage.collection('channel')
} }
async saveChannel (channel) { /**
await this.collection.insert(channel) *
} * @param {string} key
* @returns {Promise<import('chaite').Channel>}
async getChannel (id) { */
return this.collection.collection() async getItem (key) {
return this.collection.findOne({ id: key })
} }
/** /**
* *
* @param name * @param {string} id
* @returns {Promise<import('chaite').Channel[]>} * @param {import('chaite').Channel} channel
* @returns {Promise<string>}
*/ */
async getChannelByName (name) { async setItem (id, channel) {
return this.collection.find({ name }) if (id) {
} await this.collection.updateById(id, channel)
return id
async deleteChannel (name) { }
await this.collection.delete({ name }) const result = await this.collection.insert(channel)
return result.id
} }
/** /**
* 获取所有渠道 *
* @param {string?} model * @param {string} key
* @returns {Promise<void>}
*/
async removeItem (key) {
await this.collection.deleteById(key)
}
/**
*
* @returns {Promise<import('chaite').Channel[]>} * @returns {Promise<import('chaite').Channel[]>}
*/ */
async getAllChannels (model) { async listItems () {
if (model) {
return this.collection.find({ 'options.model': model })
}
return this.collection.findAll() return this.collection.findAll()
} }
/** async clear () {
* await this.collection.deleteAll()
* @param {import('chaite').ClientType} type
* @returns {Promise<Object[]>}
*/
async getChannelByType (type) {
return this.collection.find({ type })
}
/**
*
* @param {'enabled' | 'disabled'} status
* @returns {Promise<*>}
*/
async getChannelByStatus (status) {
return this.collection.find({ status })
} }
} }
export default new LowDBChannelStorage()

View file

@ -1,18 +1,16 @@
import ChatGPTStorage from '../storage.js' import ChatGPTStorage from '../storage.js'
import { ChaiteStorage } from 'chaite'
/** /**
* @returns {import('chaite').ChatPresetsStorage} * @extends {ChaiteStorage<import('chaite').ChatPreset>}
*/ */
export async function createChatPresetsStorage () { class LowDBChatPresetsStorage extends ChaiteStorage {
return new LowDBChatPresetsStorage(ChatGPTStorage)
}
class LowDBChatPresetsStorage {
/** /**
* *
* @param { LowDBStorage } storage * @param { LowDBStorage } storage
*/ */
constructor (storage) { constructor (storage = ChatGPTStorage) {
super()
this.storage = storage this.storage = storage
/** /**
* 集合 * 集合
@ -23,31 +21,48 @@ class LowDBChatPresetsStorage {
/** /**
* *
* @param {import('chaite').ChatPreset} preset * @param key
* @returns {Promise<void>} * @returns {Promise<import('chaite').ChatPreset>}
*/ */
async savePreset (preset) { async getItem (key) {
await this.collection.insert(preset)
} }
/** /**
* *
* @param { string } name * @param {string} id
* @returns {Promise<import('chaite').ChatPreset | null>} * @param {import('chaite').ChatPreset} preset
* @returns {Promise<string>}
*/ */
async getPreset (name) { async setItem (id, preset) {
return this.collection.findOne({ name }) if (id) {
await this.collection.updateById(id, preset)
return id
}
const result = await this.collection.insert(preset)
return result.id
}
/**
*
* @param {string} key
* @returns {Promise<void>}
*/
async removeItem (key) {
await this.collection.deleteById(key)
} }
/** /**
* *
* @returns {Promise<import('chaite').ChatPreset[]>} * @returns {Promise<import('chaite').ChatPreset[]>}
*/ */
async getAllPresets () { async listItems () {
return this.collection.findAll() return this.collection.findAll()
} }
async deletePreset (name) { async clear () {
await this.collection.delete({ name }) await this.collection.deleteAll()
} }
} }
export default new LowDBChatPresetsStorage()

View file

@ -1,28 +1,133 @@
import { DefaultToolCloudService, ToolManager } from 'chaite' import { Chaite, ChannelsManager, ChatPresetManager, DefaultChannelLoadBalancer, GeminiClient, OpenAIClient, ProcessorsManager, RAGManager, ToolManager } from 'chaite'
import ChatGPTConfig from '../../config/config.js' import ChatGPTConfig from '../../config/config.js'
import { createToolsSettingsStorage } from './tool_settings_storage.js' import ChatGPTChannelStorage from './channel_storage.js'
const ChatGPTToolCloudService = new DefaultToolCloudService(ChatGPTConfig.cloudBaseUrl, '', {}) import ChatPresetStorage from './chat_preset_storage.js'
/** import ChatGPTToolStorage from './tools_storage.js'
* @type {import('chaite').ToolManager} import ChatGPTProcessorsStorage from './processors_storage.js'
*/ import { ChatGPTUserModeSelector } from './user_mode_selector.js'
let ChatGPTToolManager import { LowDBUserStateStorage } from './user_state_storage.js'
ToolManager.getInstance(ChatGPTConfig.toolsDirPath, createToolsSettingsStorage(), ChatGPTToolCloudService).then((manager) => { import { LowDBHistoryManager } from './history_manager.js'
ChatGPTToolManager = manager import { ChatGPTVectorDatabase } from './vector_database.js'
})
/** /**
* 认证以便共享上传 * 认证以便共享上传
* @param apiKey * @param apiKey
* @returns {Promise<import('chaite').User>} * @returns {Promise<import('chaite').User> | null}
*/ */
export async function authCloud (apiKey) { export async function authCloud (apiKey = ChatGPTConfig.cloudApiKey) {
const user = await ChatGPTToolCloudService.authenticate(apiKey) await Chaite.getInstance().auth(apiKey)
ChatGPTToolManager.setCloudService(ChatGPTToolCloudService) return Chaite.getInstance().getToolsManager().cloudService.getUser()
return user
} }
export default { /**
ChatGPTToolCloudService, *
ChatGPTToolManager * @param {import('chaite').Channel} channel
* @returns {Promise<import('chaite').IClient>}
*/
async function getIClientByChannel (channel) {
await channel.ready()
switch (channel.adapterType) {
case 'openai': {
return new OpenAIClient(channel.options)
}
case 'gemini': {
return new GeminiClient(channel.options)
}
case 'claude': {
throw new Error('claude doesn\'t support embedding')
}
}
} }
/**
* 初始化RAG管理器
* @param {string} model
* @param {number} dimensions
*/
export async function initRagManager (model, dimensions) {
const vectorizer = new class {
async textToVector (text) {
const channels = await Chaite.getInstance().getChannelsManager().getChannelByModel(model)
if (channels.length === 0) {
throw new Error('No channel found for model: ' + model)
}
const channel = channels[0]
const client = await getIClientByChannel(channel)
const result = await client.getEmbedding(text)
return result.embeddings[0]
}
/**
*
* @param {string[]} texts
* @returns {Promise<Array<number>[]>}
*/
async batchTextToVector (texts) {
const availableChannels = (await Chaite.getInstance().getChannelsManager().getAllChannels()).filter(c => c.models.includes(model))
if (availableChannels.length === 0) {
throw new Error('No channel found for model: ' + model)
}
const channels = await Chaite.getInstance().getChannelsManager().getChannelsByModel(model, texts.length)
/**
* @type {import('chaite').IClient[]}
*/
const clients = await Promise.all(channels.map(({ channel }) => getIClientByChannel(channel)))
const results = []
let startIndex = 0
for (let i = 0; i < channels.length; i++) {
const { quantity } = channels[i]
const textsSlice = texts.slice(startIndex, startIndex + quantity)
const embeddings = await clients[i].getEmbedding(textsSlice, {
model,
dimensions
})
results.push(...embeddings.embeddings)
startIndex += quantity
}
return results
}
}()
const ragManager = new RAGManager(ChatGPTVectorDatabase, vectorizer)
return Chaite.getInstance().setRAGManager(ragManager)
}
export async function initChaite () {
const channelsManager = await ChannelsManager.init(ChatGPTChannelStorage, new DefaultChannelLoadBalancer())
const toolsManager = await ToolManager.init(ChatGPTConfig.toolsDirPath, ChatGPTToolStorage)
const processorsManager = await ProcessorsManager.init(ChatGPTConfig.processorsDirPath, ChatGPTProcessorsStorage)
const chatPresetManager = await ChatPresetManager.init(ChatPresetStorage)
const userModeSelector = new ChatGPTUserModeSelector()
const userStateStorage = new LowDBUserStateStorage()
const historyManager = new LowDBHistoryManager()
let chaite = Chaite.init(channelsManager, toolsManager, processorsManager, chatPresetManager,
userModeSelector, userStateStorage, historyManager, logger)
logger.info('Chaite 初始化完成')
chaite.setCloudService(ChatGPTConfig.cloudBaseUrl)
logger.info('Chaite.Cloud 初始化完成')
ChatGPTConfig.cloudApiKey && await chaite.auth(ChatGPTConfig.cloudApiKey)
await initRagManager(ChatGPTConfig.embeddingModel, ChatGPTConfig.dimensions)
// 监听Chaite配置变化同步需要同步的配置
chaite.on('config-change', obj => {
const { key, newVal, oldVal } = obj
if (key === 'authKey') {
ChatGPTConfig.serverAuthKey = newVal
}
logger.debug(`Chaite config changed: ${key} from ${oldVal} to ${newVal}`)
})
// 监听通过chaite对插件配置修改
chaite.setUpdateConfigCallback(config => {
logger.debug('chatgpt-plugin config updated')
Object.keys(config).forEach(key => {
ChatGPTConfig[key] = config[key]
// 回传部分需要同步的配置,以防不一致
if (key === 'serverAuthKey') {
chaite.getGlobalConfig().setAuthKey(config[key])
}
})
})
// 授予Chaite获取插件配置的能力以便通过api放出
chaite.setGetConfig(async () => {
return ChatGPTConfig
})
logger.info('Chaite.RAGManager 初始化完成')
}

View file

@ -0,0 +1,57 @@
import { AbstractHistoryManager } from 'chaite'
import ChatGPTStorage from '../storage.js'
export class LowDBHistoryManager extends AbstractHistoryManager {
/**
*
* @param { LowDBStorage } storage
*/
constructor (storage = ChatGPTStorage) {
super()
this.storage = storage
/**
* 集合
* @type {LowDBCollection}
*/
this.collection = this.storage.collection('history')
}
async saveHistory (message, conversationId) {
const historyObj = { ...message, conversationId }
if (message.id) {
await this.collection.updateById(message.id, historyObj)
}
await this.collection.insert(historyObj)
}
/**
*
* @param messageId
* @param conversationId
* @returns {Promise<import('chaite').HistoryMessage[]>}
*/
async getHistory (messageId, conversationId) {
if (messageId) {
const messages = []
let currentId = messageId
while (currentId) {
const message = await this.collection.findOne({ id: currentId })
if (!message) break
messages.unshift(message)
currentId = message.parentMessageId
}
return messages
} else if (conversationId) {
return this.collection.find({ conversationId })
}
return this.collection.findAll()
}
async deleteConversation (conversationId) {
await this.collection.delete({ conversationId })
}
async getOneHistory (messageId, conversationId) {
return this.collection.findOne({ id: messageId, conversationId })
}
}

View file

@ -0,0 +1,68 @@
import ChatGPTStorage from '../storage.js'
import { ChaiteStorage } from 'chaite'
/**
* @extends {ChaiteStorage<import('chaite').Processor>}
*/
class LowDBProcessorsStorage extends ChaiteStorage {
/**
*
* @param { LowDBStorage } storage
*/
constructor (storage = ChatGPTStorage) {
super()
this.storage = storage
/**
* 集合
* @type {LowDBCollection}
*/
this.collection = this.storage.collection('processors')
}
/**
*
* @param {string} key
* @returns {Promise<import('chaite').Processor>}
*/
async getItem (key) {
return this.collection.findOne({ id: key })
}
/**
*
* @param {string} id
* @param {import('chaite').Processor} processor
* @returns {Promise<string>}
*/
async setItem (id, processor) {
if (id) {
await this.collection.updateById(id, processor)
return id
}
const result = await this.collection.insert(processor)
return result.id
}
/**
*
* @param {string} key
* @returns {Promise<void>}
*/
async removeItem (key) {
await this.collection.deleteById(key)
}
/**
*
* @returns {Promise<import('chaite').Processor[]>}
*/
async listItems () {
return this.collection.findAll()
}
async clear () {
await this.collection.deleteAll()
}
}
export default new LowDBProcessorsStorage()

View file

@ -1,53 +0,0 @@
import ChatGPTStorage from '../storage.js'
/**
* @returns {import('chaite').ToolSettingsStorage}
*/
export function createToolsSettingsStorage () {
return new LowDBToolsSettingsStorage(ChatGPTStorage)
}
class LowDBToolsSettingsStorage {
/**
*
* @param { LowDBStorage } storage
*/
constructor (storage) {
this.storage = storage
/**
* 集合
* @type {LowDBCollection}
*/
this.collection = this.storage.collection('tool_settings')
}
/**
*
* @param {import('chaite').ToolSettings} settings
* @returns {Promise<void>}
*/
async saveToolSettings (settings) {
await this.collection.insert(settings)
}
/**
*
* @param { string } name
* @returns {Promise<import('chaite').ToolSettings | null>}
*/
async getToolSettings (name) {
return this.collection.findOne({ name })
}
/**
*
* @returns {Promise<import('chaite').ToolSettings[]>}
*/
async getAllToolSettings () {
return this.collection.findAll()
}
async deleteToolSettings (name) {
await this.collection.delete({ name })
}
}

View file

@ -0,0 +1,68 @@
import ChatGPTStorage from '../storage.js'
import { ChaiteStorage } from 'chaite'
/**
* @extends {ChaiteStorage<import('chaite').ToolDTO>}
*/
class LowDBToolSettingsStorage extends ChaiteStorage {
/**
*
* @param { LowDBStorage } storage
*/
constructor (storage = ChatGPTStorage) {
super()
this.storage = storage
/**
* 集合
* @type {LowDBCollection}
*/
this.collection = this.storage.collection('tools')
}
/**
*
* @param {string} key
* @returns {Promise<import('chaite').ToolDTO>}
*/
async getItem (key) {
return this.collection.findOne({ id: key })
}
/**
*
* @param {string} id
* @param {import('chaite').ToolDTO} tools
* @returns {Promise<string>}
*/
async setItem (id, tools) {
if (id) {
await this.collection.updateById(id, tools)
return id
}
const result = await this.collection.insert(tools)
return result.id
}
/**
*
* @param {string} key
* @returns {Promise<void>}
*/
async removeItem (key) {
await this.collection.deleteById(key)
}
/**
*
* @returns {Promise<import('chaite').ToolDTO[]>}
*/
async listItems () {
return this.collection.findAll()
}
async clear () {
await this.collection.deleteAll()
}
}
export default new LowDBToolSettingsStorage()

View file

@ -0,0 +1,12 @@
import { AbstractUserModeSelector } from '../../../../../../../WebstormProjects/node-chaite/src/types/external.js'
export class ChatGPTUserModeSelector extends AbstractUserModeSelector {
/**
* 根据e判断当前要使用的预设非常灵活
* @param e
* @returns {Promise<import('chaite').ChatPreset>}
*/
getChatPreset (e) {
// todo
}
}

View file

@ -0,0 +1,66 @@
import ChatGPTStorage from '../storage.js'
import { ChaiteStorage } from 'chaite'
/**
* @extends {ChaiteStorage<import('chaite').UserState>}
*/
export class LowDBUserStateStorage extends ChaiteStorage {
/**
*
* @param {LowDBStorage} storage
*/
constructor (storage = ChatGPTStorage) {
super()
this.storage = storage
/**
* 集合
* @type {LowDBCollection}
*/
this.collection = this.storage.collection('user_states')
}
/**
*
* @param {string} key
* @returns {Promise<import('chaite').UserState>}
*/
async getItem (key) {
return this.collection.findOne({ id: key })
}
/**
*
* @param {string} id
* @param {import('chaite').UserState} state
* @returns {Promise<string>}
*/
async setItem (id, state) {
if (id) {
await this.collection.updateById(id, state)
return id
}
const result = await this.collection.insert(state)
return result.id
}
/**
*
* @param {string} key
* @returns {Promise<void>}
*/
async removeItem (key) {
await this.collection.deleteById(key)
}
/**
*
* @returns {Promise<import('chaite').UserState[]>}
*/
async listItems () {
return this.collection.findAll()
}
async clear () {
await this.collection.deleteAll()
}
}

View file

@ -1,36 +1,90 @@
// todo import { LocalIndex } from 'vectra'
class FaissVectorDatabase { import { md5 } from '../../utils/common.js'
constructor (index) {
this.index = index /**
* 基于Vectra实现的简单向量数据库作为默认实现
*/
class VectraVectorDatabase {
constructor (indexFile) {
this.index = new LocalIndex(indexFile)
this.init()
}
async init () {
if (!(await this.index.isIndexCreated())) {
await this.index.createIndex()
}
} }
async addVector (vector, text) { async addVector (vector, text) {
const id = md5(text)
await this.index.insertItem({
vector,
id,
metadata: { text }
})
return id
} }
/**
*
* @param vectors
* @param texts
* @returns {Promise<string[]>}
*/
async addVectors (vectors, texts) { async addVectors (vectors, texts) {
return await Promise.all(vectors.map((v, i) => this.addVector(v, texts[i])))
} }
/**
*
* @param queryVector
* @param k
* @returns {Promise<Array<{ id: string, score: number, text: string }>>}
*/
async search (queryVector, k) { async search (queryVector, k) {
const results = await this.index.queryItems(queryVector, k)
return results.map(r => ({ id: r.item.id, score: r.score, text: r.item.metadata.text }))
} }
/**
*
* @param id
* @returns {Promise<{ vector: number[], text: string } | null>}
*/
async getVector (id) { async getVector (id) {
const result = await this.index.getItem(id)
return {
vector: result.vector,
text: result.metadata.text
}
} }
async deleteVector (id) { async deleteVector (id) {
await this.index.deleteItem(id)
return true
} }
async updateVector (id, newVector, newText) { async updateVector (id, newVector, newText) {
await this.index.upsertItem({
id,
vector: newVector,
metadata: { text: newText }
})
return true
} }
async count () { async count () {
return (await this.index.getIndexStats()).items
} }
async clear () { async clear () {
await this.index.deleteIndex()
} }
} }
/** /**
* 默认向量库 * 默认向量库 todo
* @type {import('chaite').VectorDatabase} * @type {import('chaite').VectorDatabase}
*/ */
export const ChatGPTVectorDatabase = new FaissVectorDatabase() export const ChatGPTVectorDatabase = new VectraVectorDatabase()

View file

@ -137,7 +137,7 @@ export class LowDBCollection {
/** /**
* 创建新文档 * 创建新文档
* @param {Object} doc 要插入的文档 * @param {Object} doc 要插入的文档
* @returns {Promise<Object>} 插入的文档带ID * @returns {Promise<Object & {id: string}>} 插入的文档带ID
*/ */
async insert (doc) { async insert (doc) {
// 生成唯一ID如果没有提供 // 生成唯一ID如果没有提供

View file

@ -4,10 +4,11 @@
"type": "module", "type": "module",
"author": "ikechan8370", "author": "ikechan8370",
"dependencies": { "dependencies": {
"chaite": "^1.0.3", "chaite": "^1.1.1",
"keyv": "^5.3.1", "keyv": "^5.3.1",
"keyv-file": "^5.1.2", "keyv-file": "^5.1.2",
"lowdb": "^7.0.1" "lowdb": "^7.0.1",
"vectra": "^0.9.0"
}, },
"pnpm": {} "pnpm": {}
} }

4
utils/common.js Normal file
View file

@ -0,0 +1,4 @@
import * as crypto from 'node:crypto'
export function md5 (str) {
return crypto.createHash('md5').update(str).digest('hex')
}