mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 18:10:26 +08:00
feat: optimize knowledge recognize (#5707)
Co-authored-by: 自由的世界人 <3196812536@qq.com>
This commit is contained in:
parent
b6520cdc9a
commit
97130cfb1e
@ -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",
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": "提示词设置",
|
||||
|
||||
@ -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": "自動取得金鑰失敗,請手動取得",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -23,6 +23,7 @@ export type Assistant = {
|
||||
webSearchProviderId?: WebSearchProvider['id']
|
||||
enableGenerateImage?: boolean
|
||||
mcpServers?: MCPServer[]
|
||||
knowledgeRecognition?: 'off' | 'on'
|
||||
}
|
||||
|
||||
export type AssistantMessage = {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user