mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-10-25 17:36:32 +08:00
54 lines
2.3 KiB
TypeScript
54 lines
2.3 KiB
TypeScript
function normalize(value: unknown): string {
|
|
return String(value ?? '').replace(/\u0000/g, '').replace(/\s+/g, ' ').trim().slice(0, 1_200).toLocaleLowerCase()
|
|
}
|
|
|
|
function searchTerms(value: string): string[] {
|
|
const normalized = normalize(value).replace(/[^\p{L}\p{N}]+/gu, ' ').trim()
|
|
const words = normalized.split(/\s+/).filter((item) => item.length >= 2)
|
|
const compact = normalized.replace(/\s+/g, '')
|
|
const bigrams: string[] = []
|
|
for (let index = 0; index < compact.length - 1 && bigrams.length < 24; index += 1) {
|
|
bigrams.push(compact.slice(index, index + 2))
|
|
}
|
|
return Array.from(new Set([...words, ...bigrams]))
|
|
}
|
|
|
|
export type AgentMemoryRelevance = {
|
|
score: number
|
|
exact: boolean
|
|
matchedTerms: string[]
|
|
}
|
|
|
|
/**
|
|
* 用精确包含、长词命中和双字覆盖率计算保守的词面相关度。
|
|
* 该分数只用于召回排序,不能作为记忆内容真实或语义相关的证明。
|
|
*/
|
|
export function agentMemoryRelevance(query: string, memoryText: string): AgentMemoryRelevance {
|
|
const normalizedQuery = normalize(query)
|
|
const normalizedMemory = normalize(memoryText)
|
|
if (!normalizedQuery || !normalizedMemory) return { score: 0, exact: false, matchedTerms: [] }
|
|
|
|
const compactQuery = normalizedQuery.replace(/[^\p{L}\p{N}]+/gu, '')
|
|
const compactMemory = normalizedMemory.replace(/[^\p{L}\p{N}]+/gu, '')
|
|
const exact = compactQuery.length >= 2 && compactMemory.includes(compactQuery)
|
|
if (exact) return { score: 1, exact: true, matchedTerms: [compactQuery] }
|
|
|
|
const terms = searchTerms(normalizedQuery)
|
|
const matchedTerms = terms.filter((term) => normalizedMemory.includes(term))
|
|
const longMatches = matchedTerms.filter((term) => term.length >= 3)
|
|
const queryBigrams = terms.filter((term) => term.length === 2)
|
|
const matchedBigrams = matchedTerms.filter((term) => term.length === 2)
|
|
const requiredBigramMatches = Math.max(2, Math.ceil(queryBigrams.length * 0.22))
|
|
if (longMatches.length === 0 && matchedBigrams.length < requiredBigramMatches) {
|
|
return { score: 0, exact: false, matchedTerms }
|
|
}
|
|
|
|
const bigramCoverage = queryBigrams.length > 0 ? matchedBigrams.length / queryBigrams.length : 0
|
|
const distinctiveCoverage = terms.length > 0 ? longMatches.length / terms.length : 0
|
|
return {
|
|
score: Math.min(0.99, bigramCoverage * 0.72 + distinctiveCoverage * 0.28),
|
|
exact: false,
|
|
matchedTerms,
|
|
}
|
|
}
|