feat: 支持GeminiPro模型

This commit is contained in:
ikechan8370 2023-12-14 17:25:55 +08:00
parent ac0aa7d02d
commit 220525fbbd
11 changed files with 415 additions and 218 deletions

View file

@ -16,11 +16,13 @@ export class BaseClient {
this.maxToken = 4096
this.tools = []
const {
e, getMessageById, upsertMessage
e, getMessageById, upsertMessage, deleteMessageById, userId
} = props
this.e = e
this.getMessageById = getMessageById
this.upsertMessage = upsertMessage
this.deleteMessageById = deleteMessageById || (() => {})
this.userId = userId
}
/**
@ -42,14 +44,23 @@ export class BaseClient {
*/
upsertMessage
/**
* delete a message with the id
*
* @type function
* @param {string} id
* @return {Promise<void>}
*/
deleteMessageById
/**
* Send prompt message with history and return response message \
* if function called, handled internally \
* override this method to implement logic of sending and receiving message
*
* @param msg
* @param opt other options, optional fields: [conversationId, parentMessageId], if not set, random uuid instead
* @returns {Promise<Message>} required fields: [text, conversationId, parentMessageId, id]
* @param {string} msg
* @param {{conversationId: string?, parentMessageId: string?, stream: boolean?, onProgress: function?}} opt other options, optional fields: [conversationId, parentMessageId], if not set, random uuid instead
* @returns {Promise<{text, conversationId, parentMessageId, id}>} required fields: [text, conversationId, parentMessageId, id]
*/
async sendMessage (msg, opt = {}) {
throw new Error('not implemented in abstract client')
@ -60,11 +71,12 @@ export class BaseClient {
* override this method to implement logic of getting history
* keyv with local file or redis recommended
*
* @param userId such as qq number
* @param opt other options
* @returns {Promise<void>}
* @param userId optional, such as qq number
* @param parentMessageId if blank, no history
* @param opt optional, other options
* @returns {Promise<object[]>}
*/
async getHistory (userId, opt = {}) {
async getHistory (parentMessageId, userId = this.userId, opt = {}) {
throw new Error('not implemented in abstract client')
}

View file

@ -0,0 +1,155 @@
import { BaseClient } from './BaseClient.js'
import { getMessageById, upsertMessage } from '../utils/common.js'
import crypto from 'crypto'
let GoogleGenerativeAI, HarmBlockThreshold, HarmCategory
try {
const GenerativeAI = await import('@google/generative-ai')
GoogleGenerativeAI = GenerativeAI.GoogleGenerativeAI
HarmBlockThreshold = GenerativeAI.HarmBlockThreshold
HarmCategory = GenerativeAI.HarmCategory
} catch (err) {
console.warn('未安装@google/generative-ai无法使用Gemini请在chatgpt-plugin目录下执行pnpm i安装新依赖')
}
export class GoogleGeminiClient extends BaseClient {
constructor (props) {
if (!props.upsertMessage) {
props.upsertMessage = async function umGemini (message) {
return await upsertMessage(message, 'Gemini')
}
}
if (!props.getMessageById) {
props.getMessageById = async function umGemini (message) {
return await getMessageById(message, 'Gemini')
}
}
super(props)
this._key = props.key
this._client = new GoogleGenerativeAI(this._key)
this.model = this._client.getGenerativeModel({ model: props.model })
this.supportFunction = false
}
async getHistory (parentMessageId, userId = this.userId, opt = {}) {
const history = []
let cursor = parentMessageId
if (!cursor) {
return history
}
do {
let parentMessage = await this.getMessageById(cursor)
if (!parentMessage) {
break
} else {
history.push(parentMessage)
cursor = parentMessage.parentMessageId
if (!cursor) {
break
}
}
} while (true)
return history.reverse()
}
async sendMessage (text, opt) {
let history = await this.getHistory(opt.parentMessageId)
let systemMessage = opt.system
if (systemMessage) {
history = history.reverse()
history.push({
role: 'model',
parts: 'ok'
})
history.push({
role: 'user',
parts: systemMessage
})
history = history.reverse()
}
const idUser = crypto.randomUUID()
const idModel = crypto.randomUUID()
let responseText = ''
try {
const chat = this.model.startChat({
history,
// [
// {
// role: 'user',
// parts: 'Hello, I have 2 dogs in my house.'
// },
// {
// role: 'model',
// parts: 'Great to meet you. What would you like to know?'
// }
// ],
generationConfig: {
// todo configuration
maxOutputTokens: 1000,
temperature: 0.9,
topP: 0.1,
topK: 16
},
safetySettings: [
// todo configuration
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_NONE
}
]
})
if (opt.stream && (typeof opt.onProgress === 'function')) {
const result = await chat.sendMessageStream(text)
responseText = ''
for await (const chunk of result.stream) {
const chunkText = chunk.text()
responseText += chunkText
await opt.onProgress(responseText)
}
return {
text: responseText,
conversationId: '',
parentMessageId: idUser,
id: idModel
}
}
const result = await chat.sendMessage(text)
const response = await result.response
responseText = response.text()
return {
text: responseText,
conversationId: '',
parentMessageId: idUser,
id: idModel
}
} finally {
await this.upsertMessage({
role: 'user',
parts: text,
id: idUser,
parentMessageId: opt.parentMessageId || undefined
})
await this.upsertMessage({
role: 'model',
parts: responseText,
id: idModel,
parentMessageId: idUser
})
}
}
async destroyHistory (conversationId, opt = {}) {
// todo clean history
}
}