mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
试验性的记忆功能 (#812)
* feat: memory basic * fix: chaite ver * fix: update prompt * fix: memory cursor and extract prompt * fix: memory retrieval bug * fix: memory retrieval bug * fix: one more attempt by codex * fix: messages prompt error * fix: one more time by codex * fix: metrics by codex * fix: memory forward * fix: memory show update time
This commit is contained in:
parent
db386ccaf2
commit
8bfce5402f
19 changed files with 4382 additions and 103 deletions
129
models/memory/userMemoryManager.js
Normal file
129
models/memory/userMemoryManager.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import { Chaite } from 'chaite'
|
||||
import * as crypto from 'node:crypto'
|
||||
import { extractUserMemories } from './extractor.js'
|
||||
import { memoryService } from './service.js'
|
||||
|
||||
const USER_MEMORY_CONTEXT_LIMIT = 6
|
||||
|
||||
export function extractTextFromContents (contents) {
|
||||
if (!Array.isArray(contents)) {
|
||||
return ''
|
||||
}
|
||||
return contents
|
||||
.filter(item => item && item.type === 'text')
|
||||
.map(item => item.text || '')
|
||||
.join('\n')
|
||||
.trim()
|
||||
}
|
||||
|
||||
export function extractTextFromUserMessage (userMessage) {
|
||||
if (!userMessage?.content) {
|
||||
return ''
|
||||
}
|
||||
return userMessage.content
|
||||
.filter(item => item.type === 'text')
|
||||
.map(item => item.text || '')
|
||||
.join('\n')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function normaliseMemoriesInput (memories, sourceId) {
|
||||
return (memories || []).map(mem => {
|
||||
if (typeof mem === 'string') {
|
||||
return {
|
||||
value: mem,
|
||||
source_message_id: sourceId
|
||||
}
|
||||
}
|
||||
if (mem && typeof mem === 'object') {
|
||||
const cloned = { ...mem }
|
||||
if (!cloned.source_message_id && sourceId) {
|
||||
cloned.source_message_id = sourceId
|
||||
}
|
||||
if (!cloned.value && cloned.fact) {
|
||||
cloned.value = cloned.fact
|
||||
}
|
||||
if (!cloned.value && cloned.text) {
|
||||
cloned.value = cloned.text
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
return {
|
||||
value: String(mem),
|
||||
source_message_id: sourceId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function processUserMemory ({ event, userMessage, userText, conversationId, assistantContents, assistantMessageId }) {
|
||||
const e = event
|
||||
if (!memoryService.isUserMemoryEnabled(e.sender.user_id)) {
|
||||
return
|
||||
}
|
||||
const snippets = []
|
||||
const userMessageId = e.message_id || e.seq || userMessage?.id || crypto.randomUUID()
|
||||
const senderName = e.sender?.card || e.sender?.nickname || String(e.sender?.user_id || '')
|
||||
|
||||
try {
|
||||
const historyManager = Chaite.getInstance()?.getHistoryManager?.()
|
||||
if (historyManager && conversationId) {
|
||||
const history = await historyManager.getHistory(null, conversationId)
|
||||
const filtered = (history || [])
|
||||
.filter(msg => ['user', 'assistant'].includes(msg.role))
|
||||
.map(msg => ({
|
||||
role: msg.role,
|
||||
text: extractTextFromContents(msg.content),
|
||||
nickname: msg.role === 'user' ? senderName : '机器人',
|
||||
message_id: msg.id
|
||||
}))
|
||||
.filter(item => item.text)
|
||||
if (filtered.length > 0) {
|
||||
const limited = filtered.slice(-USER_MEMORY_CONTEXT_LIMIT * 2)
|
||||
snippets.push(...limited)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('Failed to collect user memory context:', err)
|
||||
}
|
||||
|
||||
if (assistantContents) {
|
||||
const assistantText = extractTextFromContents(assistantContents)
|
||||
if (assistantText) {
|
||||
snippets.push({
|
||||
role: 'assistant',
|
||||
text: assistantText,
|
||||
nickname: '机器人',
|
||||
message_id: assistantMessageId || crypto.randomUUID()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (userText && !snippets.some(item => item.message_id === userMessageId)) {
|
||||
snippets.push({
|
||||
role: 'user',
|
||||
text: userText,
|
||||
nickname: senderName,
|
||||
message_id: userMessageId
|
||||
})
|
||||
}
|
||||
|
||||
if (snippets.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const existingRecords = memoryService.listUserMemories(e.sender.user_id, e.isGroup ? e.group_id : null, 50)
|
||||
const existingTexts = existingRecords.map(record => record.value).filter(Boolean)
|
||||
const memories = await extractUserMemories(snippets, existingTexts)
|
||||
if (!memories || memories.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const enriched = normaliseMemoriesInput(memories, userMessageId)
|
||||
memoryService.upsertUserMemories(
|
||||
e.sender.user_id,
|
||||
e.isGroup ? e.group_id : null,
|
||||
enriched
|
||||
)
|
||||
}
|
||||
|
||||
export { USER_MEMORY_CONTEXT_LIMIT }
|
||||
Loading…
Add table
Add a link
Reference in a new issue