feat: optimize knowledge recognize (#5707)

Co-authored-by: 自由的世界人 <3196812536@qq.com>
This commit is contained in:
Chen Tao 2025-05-07 14:34:38 +08:00 committed by GitHub
parent b6520cdc9a
commit 97130cfb1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 193 additions and 99 deletions

View File

@ -9,7 +9,6 @@
"add.prompt": "Prompt", "add.prompt": "Prompt",
"add.prompt.placeholder": "Enter prompt", "add.prompt.placeholder": "Enter prompt",
"add.title": "Create Agent", "add.title": "Create Agent",
"import.title": "Import from External",
"import": { "import": {
"title": "Import from External", "title": "Import from External",
"type": { "type": {
@ -78,7 +77,11 @@
"settings.reasoning_effort.low": "Think less", "settings.reasoning_effort.low": "Think less",
"settings.reasoning_effort.medium": "Think normally", "settings.reasoning_effort.medium": "Think normally",
"settings.reasoning_effort.default": "Default", "settings.reasoning_effort.default": "Default",
"settings.more": "Assistant Settings" "settings.more": "Assistant Settings",
"settings.knowledge_base.recognition.tip": "The assistant will use the large model's intent recognition capability to determine whether to use the knowledge base for answering. This feature will depend on the model's capabilities",
"settings.knowledge_base.recognition": "Use Knowledge Base",
"settings.knowledge_base.recognition.off": "Force Search",
"settings.knowledge_base.recognition.on": "Intent Recognition"
}, },
"auth": { "auth": {
"error": "API key automatically obtained failed, please get it manually", "error": "API key automatically obtained failed, please get it manually",
@ -1540,8 +1543,7 @@
"privacy": { "privacy": {
"title": "Privacy Settings", "title": "Privacy Settings",
"enable_privacy_mode": "Anonymous reporting of errors and statistics" "enable_privacy_mode": "Anonymous reporting of errors and statistics"
}, }
"defaultAgent": "Built-in"
}, },
"translate": { "translate": {
"any.language": "Any language", "any.language": "Any language",

View File

@ -9,7 +9,6 @@
"add.prompt": "プロンプト", "add.prompt": "プロンプト",
"add.prompt.placeholder": "プロンプトを入力", "add.prompt.placeholder": "プロンプトを入力",
"add.title": "エージェントを作成", "add.title": "エージェントを作成",
"import.title": "外部からインポート",
"import": { "import": {
"title": "外部からインポート", "title": "外部からインポート",
"type": { "type": {
@ -78,7 +77,11 @@
"settings.reasoning_effort.low": "少しの思考", "settings.reasoning_effort.low": "少しの思考",
"settings.reasoning_effort.medium": "普通の思考", "settings.reasoning_effort.medium": "普通の思考",
"settings.reasoning_effort.default": "デフォルト", "settings.reasoning_effort.default": "デフォルト",
"settings.more": "アシスタント設定" "settings.more": "アシスタント設定",
"settings.knowledge_base.recognition.tip": "アシスタントは大規模言語モデルの意図認識能力を使用して、ナレッジベースを参照する必要があるかどうかを判断します。この機能はモデルの能力に依存します",
"settings.knowledge_base.recognition": "ナレッジベースの呼び出し",
"settings.knowledge_base.recognition.off": "強制検索",
"settings.knowledge_base.recognition.on": "意図認識"
}, },
"auth": { "auth": {
"error": "APIキーの自動取得に失敗しました。手動で取得してください", "error": "APIキーの自動取得に失敗しました。手動で取得してください",
@ -1540,7 +1543,6 @@
"title": "プライバシー設定", "title": "プライバシー設定",
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信" "enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
}, },
"defaultAgent": "内蔵",
"input.show_translate_confirm": "翻訳確認ダイアログを表示" "input.show_translate_confirm": "翻訳確認ダイアログを表示"
}, },
"translate": { "translate": {

View File

@ -9,7 +9,6 @@
"add.prompt": "Промпт", "add.prompt": "Промпт",
"add.prompt.placeholder": "Введите промпт", "add.prompt.placeholder": "Введите промпт",
"add.title": "Создать агента", "add.title": "Создать агента",
"import.title": "Импорт из внешнего источника",
"delete.popup.content": "Вы уверены, что хотите удалить этого агента?", "delete.popup.content": "Вы уверены, что хотите удалить этого агента?",
"edit.message.add.title": "Добавить", "edit.message.add.title": "Добавить",
"edit.message.assistant.placeholder": "Введите сообщение ассистента", "edit.message.assistant.placeholder": "Введите сообщение ассистента",
@ -78,7 +77,11 @@
"settings.reasoning_effort.medium": "Среднее", "settings.reasoning_effort.medium": "Среднее",
"settings.reasoning_effort.default": "По умолчанию", "settings.reasoning_effort.default": "По умолчанию",
"settings.more": "Настройки ассистента", "settings.more": "Настройки ассистента",
"settings.reasoning_effort": "Настройки размышлений" "settings.reasoning_effort": "Настройки размышлений",
"settings.knowledge_base.recognition.tip": "Ассистент будет использовать возможности большой модели для распознавания намерений, чтобы определить, нужно ли обращаться к базе знаний для ответа. Эта функция будет зависеть от возможностей модели",
"settings.knowledge_base.recognition": "Использование базы знаний",
"settings.knowledge_base.recognition.off": "Принудительный поиск",
"settings.knowledge_base.recognition.on": "Распознавание намерений"
}, },
"auth": { "auth": {
"error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную", "error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную",
@ -1540,7 +1543,6 @@
"title": "Настройки конфиденциальности", "title": "Настройки конфиденциальности",
"enable_privacy_mode": "Анонимная отчетность об ошибках и статистике" "enable_privacy_mode": "Анонимная отчетность об ошибках и статистике"
}, },
"defaultAgent": "Встроенный",
"input.show_translate_confirm": "Показать диалоговое окно подтверждения перевода" "input.show_translate_confirm": "Показать диалоговое окно подтверждения перевода"
}, },
"translate": { "translate": {

View File

@ -68,6 +68,10 @@
"settings.mcp.description": "默认启用的 MCP 服务器", "settings.mcp.description": "默认启用的 MCP 服务器",
"settings.default_model": "默认模型", "settings.default_model": "默认模型",
"settings.knowledge_base": "知识库设置", "settings.knowledge_base": "知识库设置",
"settings.knowledge_base.recognition.tip": "智能体将调用大模型的意图识别能力,判断是否需要调用知识库进行回答,该功能将依赖模型的能力",
"settings.knowledge_base.recognition": "调用知识库",
"settings.knowledge_base.recognition.off": "强制检索",
"settings.knowledge_base.recognition.on": "意图识别",
"settings.model": "模型设置", "settings.model": "模型设置",
"settings.preset_messages": "预设消息", "settings.preset_messages": "预设消息",
"settings.prompt": "提示词设置", "settings.prompt": "提示词设置",

View File

@ -77,7 +77,11 @@
"settings.reasoning_effort.low": "稍微思考", "settings.reasoning_effort.low": "稍微思考",
"settings.reasoning_effort.medium": "正常思考", "settings.reasoning_effort.medium": "正常思考",
"settings.reasoning_effort.default": "預設", "settings.reasoning_effort.default": "預設",
"settings.more": "助手設定" "settings.more": "助手設定",
"settings.knowledge_base.recognition.tip": "智慧代理人將調用大語言模型的意圖識別能力,判斷是否需要調用知識庫進行回答,該功能將依賴模型的能力",
"settings.knowledge_base.recognition": "調用知識庫",
"settings.knowledge_base.recognition.off": "強制檢索",
"settings.knowledge_base.recognition.on": "意圖識別"
}, },
"auth": { "auth": {
"error": "自動取得金鑰失敗,請手動取得", "error": "自動取得金鑰失敗,請手動取得",

View File

@ -2,7 +2,8 @@ import { CheckOutlined } from '@ant-design/icons'
import { Box } from '@renderer/components/Layout' import { Box } from '@renderer/components/Layout'
import { useAppSelector } from '@renderer/store' import { useAppSelector } from '@renderer/store'
import { Assistant, AssistantSettings } from '@renderer/types' import { Assistant, AssistantSettings } from '@renderer/types'
import { Select, SelectProps } from 'antd' import { Row, Segmented, Select, SelectProps, Tooltip } from 'antd'
import { CircleHelp } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -46,6 +47,34 @@ const AssistantKnowledgeBaseSettings: React.FC<Props> = ({ assistant, updateAssi
.includes(input.toLowerCase()) .includes(input.toLowerCase())
} }
/> />
<Row align="middle" style={{ marginTop: 10 }}>
<Label>{t('assistants.settings.knowledge_base.recognition')}</Label>
</Row>
<Row align="middle" style={{ marginTop: 10 }}>
<Segmented
value={assistant.knowledgeRecognition ?? 'off'}
options={[
{ label: t('assistants.settings.knowledge_base.recognition.off'), value: 'off' },
{
label: (
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
{t('assistants.settings.knowledge_base.recognition.on')}
<Tooltip title={t('assistants.settings.knowledge_base.recognition.tip')}>
<QuestionIcon size={15} />
</Tooltip>
</div>
),
value: 'on'
}
]}
onChange={(value) =>
updateAssistant({
...assistant,
knowledgeRecognition: value as 'off' | 'on'
})
}
/>
</Row>
</Container> </Container>
) )
} }
@ -57,5 +86,13 @@ const Container = styled.div`
overflow: hidden; overflow: hidden;
padding: 5px; padding: 5px;
` `
const Label = styled.p`
margin-right: 5px;
font-weight: 500;
`
const QuestionIcon = styled(CircleHelp)`
cursor: pointer;
color: var(--color-text-3);
`
export default AssistantKnowledgeBaseSettings export default AssistantKnowledgeBaseSettings

View File

@ -46,22 +46,32 @@ async function fetchExternalTool(
// 可能会有重复? // 可能会有重复?
const knowledgeBaseIds = getKnowledgeBaseIds(lastUserMessage) const knowledgeBaseIds = getKnowledgeBaseIds(lastUserMessage)
const hasKnowledgeBase = !isEmpty(knowledgeBaseIds) const hasKnowledgeBase = !isEmpty(knowledgeBaseIds)
const knowledgeRecognition = assistant.knowledgeRecognition || 'on'
const webSearchProvider = WebSearchService.getWebSearchProvider(assistant.webSearchProviderId) const webSearchProvider = WebSearchService.getWebSearchProvider(assistant.webSearchProviderId)
const shouldWebSearch = !!assistant.webSearchProviderId && webSearchProvider !== null
const shouldKnowledgeSearch = hasKnowledgeBase
// 在工具链开始时发送进度通知
const willUseTools = shouldWebSearch || shouldKnowledgeSearch
if (willUseTools) {
onChunkReceived({ type: ChunkType.EXTERNEL_TOOL_IN_PROGRESS })
}
// --- Keyword/Question Extraction Function --- // --- Keyword/Question Extraction Function ---
const extract = async (): Promise<ExtractResults | undefined> => { const extract = async (): Promise<ExtractResults | undefined> => {
if (!lastUserMessage) return undefined if (!lastUserMessage) return undefined
// 如果都不需要搜索,则直接返回,不意图识别
if (!shouldWebSearch && !hasKnowledgeBase) return undefined
// Notify UI that extraction/searching is starting // 根据配置决定是否需要提取
onChunkReceived({ type: ChunkType.EXTERNEL_TOOL_IN_PROGRESS }) const needWebExtract = shouldWebSearch
const needKnowledgeExtract = hasKnowledgeBase && knowledgeRecognition === 'on'
let prompt = '' if (!needWebExtract && !needKnowledgeExtract) return undefined
if (shouldWebSearch && !hasKnowledgeBase) { let prompt: string
if (needWebExtract && !needKnowledgeExtract) {
prompt = SEARCH_SUMMARY_PROMPT_WEB_ONLY prompt = SEARCH_SUMMARY_PROMPT_WEB_ONLY
} else if (!shouldWebSearch && hasKnowledgeBase) { } else if (!needWebExtract && needKnowledgeExtract) {
prompt = SEARCH_SUMMARY_PROMPT_KNOWLEDGE_ONLY prompt = SEARCH_SUMMARY_PROMPT_KNOWLEDGE_ONLY
} else { } else {
prompt = SEARCH_SUMMARY_PROMPT prompt = SEARCH_SUMMARY_PROMPT
@ -71,29 +81,20 @@ async function fetchExternalTool(
summaryAssistant.model = assistant.model || getDefaultModel() summaryAssistant.model = assistant.model || getDefaultModel()
summaryAssistant.prompt = prompt summaryAssistant.prompt = prompt
const getFallbackResult = (): ExtractResults => {
const fallbackContent = getMainTextContent(lastUserMessage)
return {
websearch: shouldWebSearch
? {
question: [fallbackContent || 'search']
}
: undefined,
knowledge: hasKnowledgeBase
? {
question: [fallbackContent || 'search']
}
: undefined
} as ExtractResults
}
try { try {
const keywords = await fetchSearchSummary({ const keywords = await fetchSearchSummary({
messages: lastAnswer ? [lastAnswer, lastUserMessage] : [lastUserMessage], messages: lastAnswer ? [lastAnswer, lastUserMessage] : [lastUserMessage],
assistant: summaryAssistant assistant: summaryAssistant
}) })
return keywords ? extractInfoFromXML(keywords) : getFallbackResult() if (!keywords) return getFallbackResult()
const extracted = extractInfoFromXML(keywords)
// 根据需求过滤结果
return {
websearch: needWebExtract ? extracted?.websearch : undefined,
knowledge: needKnowledgeExtract ? extracted?.knowledge : undefined
}
} catch (e: any) { } catch (e: any) {
console.error('extract error', e) console.error('extract error', e)
if (isAbortError(e)) throw e if (isAbortError(e)) throw e
@ -101,16 +102,29 @@ async function fetchExternalTool(
} }
} }
const getFallbackResult = (): ExtractResults => {
const fallbackContent = getMainTextContent(lastUserMessage)
return {
websearch: shouldWebSearch ? { question: [fallbackContent || 'search'] } : undefined,
knowledge: shouldKnowledgeSearch
? {
question: [fallbackContent || 'search'],
rewrite: fallbackContent
}
: undefined
}
}
// --- Web Search Function --- // --- Web Search Function ---
const searchTheWeb = async (): Promise<WebSearchResponse | undefined> => { const searchTheWeb = async (extractResults: ExtractResults | undefined): Promise<WebSearchResponse | undefined> => {
if (!shouldWebSearch) return
// Add check for extractResults existence early // Add check for extractResults existence early
if (!extractResults?.websearch) { if (!extractResults?.websearch) {
console.warn('searchTheWeb called without valid extractResults.websearch') console.warn('searchTheWeb called without valid extractResults.websearch')
return return
} }
if (!shouldWebSearch) return
// Add check for assistant.model before using it // Add check for assistant.model before using it
if (!assistant.model) { if (!assistant.model) {
console.warn('searchTheWeb called without assistant.model') console.warn('searchTheWeb called without assistant.model')
@ -138,93 +152,121 @@ async function fetchExternalTool(
} }
// --- Knowledge Base Search Function --- // --- Knowledge Base Search Function ---
const searchKnowledgeBase = async (): Promise<KnowledgeReference[] | undefined> => { const searchKnowledgeBase = async (
// Add check for extractResults existence early extractResults: ExtractResults | undefined
if (!extractResults?.knowledge) { ): Promise<KnowledgeReference[] | undefined> => {
console.warn('searchKnowledgeBase called without valid extractResults.knowledge') if (!hasKnowledgeBase) return
return
// 知识库搜索条件
let searchCriteria: { question: string[]; rewrite: string }
if (knowledgeRecognition === 'off') {
const directContent = getMainTextContent(lastUserMessage)
searchCriteria = { question: [directContent || 'search'], rewrite: directContent }
} else {
// auto mode
if (!extractResults?.knowledge) {
console.warn('searchKnowledgeBase: No valid search criteria in auto mode')
return
}
searchCriteria = extractResults.knowledge
} }
const shouldSearch = hasKnowledgeBase && extractResults.knowledge.question[0] !== 'not_needed' if (searchCriteria.question[0] === 'not_needed') return
if (!shouldSearch) return
try { try {
const tempExtractResults: ExtractResults = {
websearch: undefined,
knowledge: searchCriteria
}
// Attempt to get knowledgeBaseIds from the main text block // Attempt to get knowledgeBaseIds from the main text block
// NOTE: This assumes knowledgeBaseIds are ONLY on the main text block // NOTE: This assumes knowledgeBaseIds are ONLY on the main text block
// NOTE: processKnowledgeSearch needs to handle undefined ids gracefully // NOTE: processKnowledgeSearch needs to handle undefined ids gracefully
// const mainTextBlock = mainTextBlocks // const mainTextBlock = mainTextBlocks
// ?.map((blockId) => store.getState().messageBlocks.entities[blockId]) // ?.map((blockId) => store.getState().messageBlocks.entities[blockId])
// .find((block) => block?.type === MessageBlockType.MAIN_TEXT) as MainTextMessageBlock | undefined // .find((block) => block?.type === MessageBlockType.MAIN_TEXT) as MainTextMessageBlock | undefined
return await processKnowledgeSearch(extractResults, knowledgeBaseIds) return await processKnowledgeSearch(tempExtractResults, knowledgeBaseIds)
} catch (error) { } catch (error) {
console.error('Knowledge base search failed:', error) console.error('Knowledge base search failed:', error)
return return
} }
} }
const shouldWebSearch = !!assistant.webSearchProviderId
// --- Execute Extraction and Searches --- // --- Execute Extraction and Searches ---
const extractResults = await extract() let extractResults: ExtractResults | undefined
// Run searches potentially in parallel
let webSearchResponseFromSearch: WebSearchResponse | undefined try {
let knowledgeReferencesFromSearch: KnowledgeReference[] | undefined // 根据配置决定是否需要提取
const isWebSearchValid = extractResults?.websearch && assistant.model if (shouldWebSearch || hasKnowledgeBase) {
const isKnowledgeSearchValid = extractResults?.knowledge extractResults = await extract()
const isAllValidSearch = lastUserMessage && (isKnowledgeSearchValid || isWebSearchValid) console.log('Extraction results:', extractResults)
}
if (isAllValidSearch) { let webSearchResponseFromSearch: WebSearchResponse | undefined
// TODO: 应该在这写search开始 let knowledgeReferencesFromSearch: KnowledgeReference[] | undefined
if (isKnowledgeSearchValid && isWebSearchValid) {
// 并行执行搜索
if (shouldWebSearch || shouldKnowledgeSearch) {
;[webSearchResponseFromSearch, knowledgeReferencesFromSearch] = await Promise.all([ ;[webSearchResponseFromSearch, knowledgeReferencesFromSearch] = await Promise.all([
searchTheWeb(), searchTheWeb(extractResults),
searchKnowledgeBase() searchKnowledgeBase(extractResults)
]) ])
} else if (isKnowledgeSearchValid) {
knowledgeReferencesFromSearch = await searchKnowledgeBase()
} else if (isWebSearchValid) {
webSearchResponseFromSearch = await searchTheWeb()
} }
// Search判断很准确了可以在这写search结束
onChunkReceived({ // 存储搜索结果
type: ChunkType.EXTERNEL_TOOL_COMPLETE, if (lastUserMessage) {
external_tool: { if (webSearchResponseFromSearch) {
webSearch: webSearchResponseFromSearch, window.keyv.set(`web-search-${lastUserMessage.id}`, webSearchResponseFromSearch)
knowledge: knowledgeReferencesFromSearch }
if (knowledgeReferencesFromSearch) {
window.keyv.set(`knowledge-search-${lastUserMessage.id}`, knowledgeReferencesFromSearch)
} }
})
}
// --- Prepare for AI Completion ---
// Store results temporarily (e.g., using window.keyv like before)
if (lastUserMessage) {
if (webSearchResponseFromSearch) {
window.keyv.set(`web-search-${lastUserMessage.id}`, webSearchResponseFromSearch)
} }
if (knowledgeReferencesFromSearch) {
window.keyv.set(`knowledge-search-${lastUserMessage.id}`, knowledgeReferencesFromSearch)
}
}
// Get MCP tools (Fix duplicate declaration) // 发送工具执行完成通知
let mcpTools: MCPTool[] = [] // Initialize as empty array if (willUseTools) {
const enabledMCPs = lastUserMessage?.enabledMCPs onChunkReceived({
if (enabledMCPs && enabledMCPs.length > 0) { type: ChunkType.EXTERNEL_TOOL_COMPLETE,
try { external_tool: {
const toolPromises = enabledMCPs.map(async (mcpServer) => { webSearch: webSearchResponseFromSearch,
const tools = await window.api.mcp.listTools(mcpServer) knowledge: knowledgeReferencesFromSearch
return tools.filter((tool: any) => !mcpServer.disabledTools?.includes(tool.name)) }
}) })
const results = await Promise.all(toolPromises)
mcpTools = results.flat() // Flatten the array of arrays
} catch (toolError) {
console.error('Error fetching MCP tools:', toolError)
} }
}
return { mcpTools } // Get MCP tools (Fix duplicate declaration)
let mcpTools: MCPTool[] = [] // Initialize as empty array
const enabledMCPs = lastUserMessage?.enabledMCPs
if (enabledMCPs && enabledMCPs.length > 0) {
try {
const toolPromises = enabledMCPs.map(async (mcpServer) => {
const tools = await window.api.mcp.listTools(mcpServer)
return tools.filter((tool: any) => !mcpServer.disabledTools?.includes(tool.name))
})
const results = await Promise.all(toolPromises)
mcpTools = results.flat() // Flatten the array of arrays
} catch (toolError) {
console.error('Error fetching MCP tools:', toolError)
}
}
return { mcpTools }
} catch (error) {
console.error('Tool execution failed:', error)
if (isAbortError(error)) throw error
// 发送错误状态
if (willUseTools) {
onChunkReceived({
type: ChunkType.EXTERNEL_TOOL_COMPLETE,
external_tool: {
webSearch: undefined,
knowledge: undefined
}
})
}
return { mcpTools: [] }
}
} }
export async function fetchChatCompletion({ export async function fetchChatCompletion({

View File

@ -23,6 +23,7 @@ export type Assistant = {
webSearchProviderId?: WebSearchProvider['id'] webSearchProviderId?: WebSearchProvider['id']
enableGenerateImage?: boolean enableGenerateImage?: boolean
mcpServers?: MCPServer[] mcpServers?: MCPServer[]
knowledgeRecognition?: 'off' | 'on'
} }
export type AssistantMessage = { export type AssistantMessage = {

View File

@ -28,6 +28,6 @@ export const extractInfoFromXML = (text: string): ExtractResults => {
} }
}) })
const extractResults: ExtractResults = parser.parse(text) const extractResults: ExtractResults = parser.parse(text)
console.log('Extracted results:', extractResults) // console.log('Extracted results:', extractResults)
return extractResults return extractResults
} }