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.placeholder": "Enter prompt",
"add.title": "Create Agent",
"import.title": "Import from External",
"import": {
"title": "Import from External",
"type": {
@ -78,7 +77,11 @@
"settings.reasoning_effort.low": "Think less",
"settings.reasoning_effort.medium": "Think normally",
"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": {
"error": "API key automatically obtained failed, please get it manually",
@ -1540,8 +1543,7 @@
"privacy": {
"title": "Privacy Settings",
"enable_privacy_mode": "Anonymous reporting of errors and statistics"
},
"defaultAgent": "Built-in"
}
},
"translate": {
"any.language": "Any language",

View File

@ -9,7 +9,6 @@
"add.prompt": "プロンプト",
"add.prompt.placeholder": "プロンプトを入力",
"add.title": "エージェントを作成",
"import.title": "外部からインポート",
"import": {
"title": "外部からインポート",
"type": {
@ -78,7 +77,11 @@
"settings.reasoning_effort.low": "少しの思考",
"settings.reasoning_effort.medium": "普通の思考",
"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": {
"error": "APIキーの自動取得に失敗しました。手動で取得してください",
@ -1540,7 +1543,6 @@
"title": "プライバシー設定",
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
},
"defaultAgent": "内蔵",
"input.show_translate_confirm": "翻訳確認ダイアログを表示"
},
"translate": {

View File

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

View File

@ -68,6 +68,10 @@
"settings.mcp.description": "默认启用的 MCP 服务器",
"settings.default_model": "默认模型",
"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.preset_messages": "预设消息",
"settings.prompt": "提示词设置",

View File

@ -77,7 +77,11 @@
"settings.reasoning_effort.low": "稍微思考",
"settings.reasoning_effort.medium": "正常思考",
"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": {
"error": "自動取得金鑰失敗,請手動取得",

View File

@ -2,7 +2,8 @@ import { CheckOutlined } from '@ant-design/icons'
import { Box } from '@renderer/components/Layout'
import { useAppSelector } from '@renderer/store'
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 styled from 'styled-components'
@ -46,6 +47,34 @@ const AssistantKnowledgeBaseSettings: React.FC<Props> = ({ assistant, updateAssi
.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>
)
}
@ -57,5 +86,13 @@ const Container = styled.div`
overflow: hidden;
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

View File

@ -46,22 +46,32 @@ async function fetchExternalTool(
// 可能会有重复?
const knowledgeBaseIds = getKnowledgeBaseIds(lastUserMessage)
const hasKnowledgeBase = !isEmpty(knowledgeBaseIds)
const knowledgeRecognition = assistant.knowledgeRecognition || 'on'
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 ---
const extract = async (): Promise<ExtractResults | 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
} else if (!shouldWebSearch && hasKnowledgeBase) {
} else if (!needWebExtract && needKnowledgeExtract) {
prompt = SEARCH_SUMMARY_PROMPT_KNOWLEDGE_ONLY
} else {
prompt = SEARCH_SUMMARY_PROMPT
@ -71,29 +81,20 @@ async function fetchExternalTool(
summaryAssistant.model = assistant.model || getDefaultModel()
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 {
const keywords = await fetchSearchSummary({
messages: lastAnswer ? [lastAnswer, lastUserMessage] : [lastUserMessage],
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) {
console.error('extract error', 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 ---
const searchTheWeb = async (): Promise<WebSearchResponse | undefined> => {
const searchTheWeb = async (extractResults: ExtractResults | undefined): Promise<WebSearchResponse | undefined> => {
if (!shouldWebSearch) return
// Add check for extractResults existence early
if (!extractResults?.websearch) {
console.warn('searchTheWeb called without valid extractResults.websearch')
return
}
if (!shouldWebSearch) return
// Add check for assistant.model before using it
if (!assistant.model) {
console.warn('searchTheWeb called without assistant.model')
@ -138,67 +152,67 @@ async function fetchExternalTool(
}
// --- Knowledge Base Search Function ---
const searchKnowledgeBase = async (): Promise<KnowledgeReference[] | undefined> => {
// Add check for extractResults existence early
const searchKnowledgeBase = async (
extractResults: ExtractResults | undefined
): Promise<KnowledgeReference[] | undefined> => {
if (!hasKnowledgeBase) 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 called without valid 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 (!shouldSearch) return
if (searchCriteria.question[0] === 'not_needed') return
try {
const tempExtractResults: ExtractResults = {
websearch: undefined,
knowledge: searchCriteria
}
// Attempt to get knowledgeBaseIds from the main text block
// NOTE: This assumes knowledgeBaseIds are ONLY on the main text block
// NOTE: processKnowledgeSearch needs to handle undefined ids gracefully
// const mainTextBlock = mainTextBlocks
// ?.map((blockId) => store.getState().messageBlocks.entities[blockId])
// .find((block) => block?.type === MessageBlockType.MAIN_TEXT) as MainTextMessageBlock | undefined
return await processKnowledgeSearch(extractResults, knowledgeBaseIds)
return await processKnowledgeSearch(tempExtractResults, knowledgeBaseIds)
} catch (error) {
console.error('Knowledge base search failed:', error)
return
}
}
const shouldWebSearch = !!assistant.webSearchProviderId
// --- Execute Extraction and Searches ---
const extractResults = await extract()
// Run searches potentially in parallel
let extractResults: ExtractResults | undefined
try {
// 根据配置决定是否需要提取
if (shouldWebSearch || hasKnowledgeBase) {
extractResults = await extract()
console.log('Extraction results:', extractResults)
}
let webSearchResponseFromSearch: WebSearchResponse | undefined
let knowledgeReferencesFromSearch: KnowledgeReference[] | undefined
const isWebSearchValid = extractResults?.websearch && assistant.model
const isKnowledgeSearchValid = extractResults?.knowledge
const isAllValidSearch = lastUserMessage && (isKnowledgeSearchValid || isWebSearchValid)
if (isAllValidSearch) {
// TODO: 应该在这写search开始
if (isKnowledgeSearchValid && isWebSearchValid) {
// 并行执行搜索
if (shouldWebSearch || shouldKnowledgeSearch) {
;[webSearchResponseFromSearch, knowledgeReferencesFromSearch] = await Promise.all([
searchTheWeb(),
searchKnowledgeBase()
searchTheWeb(extractResults),
searchKnowledgeBase(extractResults)
])
} else if (isKnowledgeSearchValid) {
knowledgeReferencesFromSearch = await searchKnowledgeBase()
} else if (isWebSearchValid) {
webSearchResponseFromSearch = await searchTheWeb()
}
// Search判断很准确了可以在这写search结束
onChunkReceived({
type: ChunkType.EXTERNEL_TOOL_COMPLETE,
external_tool: {
webSearch: webSearchResponseFromSearch,
knowledge: 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)
@ -208,6 +222,17 @@ async function fetchExternalTool(
}
}
// 发送工具执行完成通知
if (willUseTools) {
onChunkReceived({
type: ChunkType.EXTERNEL_TOOL_COMPLETE,
external_tool: {
webSearch: webSearchResponseFromSearch,
knowledge: knowledgeReferencesFromSearch
}
})
}
// Get MCP tools (Fix duplicate declaration)
let mcpTools: MCPTool[] = [] // Initialize as empty array
const enabledMCPs = lastUserMessage?.enabledMCPs
@ -225,6 +250,23 @@ async function fetchExternalTool(
}
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({

View File

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

View File

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