diff --git a/src/renderer/src/config/constant.ts b/src/renderer/src/config/constant.ts index 6c169f23..f3229bdc 100644 --- a/src/renderer/src/config/constant.ts +++ b/src/renderer/src/config/constant.ts @@ -2,6 +2,7 @@ export const DEFAULT_TEMPERATURE = 1.0 export const DEFAULT_CONTEXTCOUNT = 5 export const DEFAULT_MAX_TOKENS = 4096 export const DEFAULT_KNOWLEDGE_DOCUMENT_COUNT = 6 +export const DEFAULT_KNOWLEDGE_THRESHOLD = 0.0 export const FONT_FAMILY = "Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif" diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index bf3401ab..b1016d83 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -296,7 +296,12 @@ "title": "Knowledge Base", "url_added": "URL added", "url_placeholder": "Enter URL, multiple URLs separated by Enter", - "urls": "URLs" + "urls": "URLs", + "threshold_tooltip": "Match threshold", + "threshold_placeholder": "Default value (0.0)", + "threshold_too_large_or_small": "Threshold cannot be greater than 1 or less than 0", + "no_match": "No matching content found in the knowledge base.", + "threshold": "Matching threshold" }, "languages": { "arabic": "Arabic", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index b71ecc11..62052c3c 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -296,7 +296,12 @@ "title": "ナレッジベース", "url_added": "URLが追加されました", "url_placeholder": "URLを入力, 複数のURLはEnterで区切る", - "urls": "URL" + "urls": "URL", + "threshold_tooltip": "マッチングしきい値", + "threshold_placeholder": "デフォルト値(0.0)", + "threshold_too_large_or_small": "しきい値は0より大きく1より小さい必要があります", + "no_match": "知識ベースの内容が見つかりませんでした。", + "threshold": "マッチング度閾値" }, "languages": { "arabic": "アラビア語", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 738f3ec4..22bcf4e6 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -296,7 +296,12 @@ "title": "База знаний", "url_added": "URL добавлен", "url_placeholder": "Введите URL, несколько URL через Enter", - "urls": "URL-адреса" + "urls": "URL-адреса", + "threshold_tooltip": "Порог совпадения", + "threshold_placeholder": "По умолчанию (0.0)", + "threshold_too_large_or_small": "Порог не может быть больше 1 или меньше 0", + "no_match": "Не найдено содержимого в базе знаний.", + "threshold": "Порог соответствия" }, "languages": { "arabic": "Арабский", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 262b0c9d..4370f645 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -275,6 +275,7 @@ "invalid_url": "无效的网址", "model_info": "模型信息", "no_bases": "暂无知识库", + "no_match": "未匹配到知识库内容", "no_provider": "知识库模型服务商丢失,该知识库将不再支持,请重新创建知识库", "not_set": "未设置", "not_support": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库", @@ -293,6 +294,10 @@ "status_new": "已添加", "status_pending": "等待中", "status_processing": "处理中", + "threshold": "匹配度阈值", + "threshold_tooltip": "用于衡量用户问题与知识库内容之间的相关性", + "threshold_placeholder": "默认值(0.0)", + "threshold_too_large_or_small": "阈值不能大于1或小于0", "title": "知识库", "url_added": "网址已添加", "url_placeholder": "请输入网址, 多个网址用回车分隔", @@ -768,4 +773,4 @@ "title": "帮助文档" } } -} +} \ No newline at end of file diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 20f9dd64..523c8e6c 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -296,7 +296,12 @@ "title": "知識庫", "url_added": "網址已添加", "url_placeholder": "請輸入網址, 多個網址用回車分隔", - "urls": "網址" + "urls": "網址", + "threshold_tooltip": "匹配度閾值", + "threshold_placeholder": "預設值(0.0)", + "threshold_too_large_or_small": "閾值不能大於1或小於0", + "no_match": "未匹配到知識庫內容", + "threshold": "匹配度閾值" }, "languages": { "arabic": "阿拉伯文", @@ -449,9 +454,6 @@ "title": "LM Studio" }, "paintings": { - "infini": "無問芯穹", - "perplexity": "Perplexity", - "dmxapi": "DMXAPI", "button.delete.image": "刪除繪圖", "button.delete.image.confirm": "確定要刪除此繪圖嗎?", "button.new.image": "新繪圖", @@ -513,7 +515,10 @@ "together": "Together", "yi": "零一萬物", "zhinao": "360智腦", - "zhipu": "智譜AI" + "zhipu": "智譜AI", + "infini": "無問芯穹", + "perplexity": "Perplexity", + "dmxapi": "DMXAPI" }, "settings": { "about": "關於與回饋", diff --git a/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx b/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx index 9d727b58..ac04ddac 100644 --- a/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx +++ b/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx @@ -1,5 +1,6 @@ import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' import { TopView } from '@renderer/components/TopView' +import { DEFAULT_KNOWLEDGE_THRESHOLD } from '@renderer/config/constant' import { getFileFromUrl, getKnowledgeBaseParams } from '@renderer/services/KnowledgeService' import { FileType, KnowledgeBase } from '@renderer/types' import { Input, List, Modal, Spin, Typography } from 'antd' @@ -45,7 +46,11 @@ const PopupContainer: React.FC = ({ base, resolve }) => { return { ...item, file } }) ) - setResults(results) + const filteredResults = results.filter((item) => { + const threshold = base.threshold || DEFAULT_KNOWLEDGE_THRESHOLD + return item.score >= threshold + }) + setResults(filteredResults) } catch (error) { console.error('Search failed:', error) } finally { diff --git a/src/renderer/src/pages/knowledge/components/KnowledgeSettingsPopup.tsx b/src/renderer/src/pages/knowledge/components/KnowledgeSettingsPopup.tsx index 0a4d1180..8dc16e1d 100644 --- a/src/renderer/src/pages/knowledge/components/KnowledgeSettingsPopup.tsx +++ b/src/renderer/src/pages/knowledge/components/KnowledgeSettingsPopup.tsx @@ -22,6 +22,7 @@ interface FormData { documentCount?: number chunkSize?: number chunkOverlap?: number + threshold?: number } interface Props extends ShowParams { @@ -66,7 +67,8 @@ const PopupContainer: React.FC = ({ base: _base, resolve }) => { name: values.name, documentCount: values.documentCount || DEFAULT_KNOWLEDGE_DOCUMENT_COUNT, chunkSize: values.chunkSize, - chunkOverlap: values.chunkOverlap + chunkOverlap: values.chunkOverlap, + threshold: values.threshold } updateKnowledgeBase(newBase) setOpen(false) @@ -174,6 +176,23 @@ const PopupContainer: React.FC = ({ base: _base, resolve }) => { placeholder={t('knowledge.chunk_overlap_placeholder')} /> + 1 || value < 0)) { + return Promise.reject(new Error(t('knowledge.threshold_too_large_or_small'))) + } + return Promise.resolve() + } + } + ]}> + + } /> diff --git a/src/renderer/src/providers/BaseProvider.ts b/src/renderer/src/providers/BaseProvider.ts index 73ced133..6cfe544f 100644 --- a/src/renderer/src/providers/BaseProvider.ts +++ b/src/renderer/src/providers/BaseProvider.ts @@ -90,9 +90,14 @@ export default abstract class BaseProvider { return message.content } - const references = await getKnowledgeReferences(base, message) + const { referencesContent, referencesCount } = await getKnowledgeReferences(base, message) - return REFERENCE_PROMPT.replace('{question}', message.content).replace('{references}', references) + // 如果知识库中未检索到内容则使用通用逻辑 + if (referencesCount === 0) { + return message.content + } + + return REFERENCE_PROMPT.replace('{question}', message.content).replace('{references}', referencesContent) } protected getCustomParameters(assistant: Assistant) { diff --git a/src/renderer/src/services/KnowledgeService.ts b/src/renderer/src/services/KnowledgeService.ts index ae6db6ac..74bdd2ab 100644 --- a/src/renderer/src/services/KnowledgeService.ts +++ b/src/renderer/src/services/KnowledgeService.ts @@ -1,8 +1,9 @@ import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces' -import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT } from '@renderer/config/constant' +import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT, DEFAULT_KNOWLEDGE_THRESHOLD } from '@renderer/config/constant' import { getEmbeddingMaxContext } from '@renderer/config/embedings' import AiProvider from '@renderer/providers/AiProvider' import { FileType, KnowledgeBase, KnowledgeBaseParams, Message } from '@renderer/types' +import { t } from 'i18next' import { take } from 'lodash' import { getProviderByModel } from './AssistantService' @@ -79,10 +80,25 @@ export const getKnowledgeSourceUrl = async (item: ExtractChunkData & { file: Fil } export const getKnowledgeReferences = async (base: KnowledgeBase, message: Message) => { - const searchResults = await window.api.knowledgeBase.search({ - search: message.content, - base: getKnowledgeBaseParams(base) - }) + const searchResults = await window.api.knowledgeBase + .search({ + search: message.content, + base: getKnowledgeBaseParams(base) + }) + .then((results) => + results.filter((item) => { + const threshold = base.threshold || DEFAULT_KNOWLEDGE_THRESHOLD + return item.score >= threshold + }) + ) + if (searchResults.length === 0) { + window.message.info({ + content: t('knowledge.no_match'), + duration: 4, + key: 'knowledge-base-no-match-info' + }) + return { referencesContent: '', referencesCount: 0 } + } const _searchResults = await Promise.all( searchResults.map(async (item) => { @@ -107,5 +123,5 @@ export const getKnowledgeReferences = async (base: KnowledgeBase, message: Messa const referencesContent = `\`\`\`json\n${JSON.stringify(references, null, 2)}\n\`\`\`` - return referencesContent + return { referencesContent, referencesCount: references.length } } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index b77f5b55..e8388b27 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -244,6 +244,7 @@ export interface KnowledgeBase { documentCount?: number chunkSize?: number chunkOverlap?: number + threshold?: number } export type KnowledgeBaseParams = {