fix: stop external knowledge chat retrieval from running binding checks

Conversation retrieval called the binding verification helper, so every
question sent the user query as a test query and RAGFlow bindings with graph
or knowledge compilation enabled spent an extra detail request before
retrieving. A failed capability check could also surface
EXTERNAL_KB_CAPABILITY_UNAVAILABLE during a normal conversation.

Retrieval and verification are now separate service entry points sharing one
snippet-bounding helper. The capability pre-check stays in the verification
path and keeps using the binding's configured request timeout.

Replace the five configuration snapshot comparisons and the
EXTERNAL_KB_CONFIG_CHANGED code with the existing per-instance cancellation.
A connection test cancelled by a configuration change now rejects without
writing back a probe result, so disabling an instance no longer records it as
a failed probe. Duplicate binding safety relies on a re-check plus the
existing unique index.

Release note: 外部知识库对话检索不再执行仅用于保存校验的能力预检,RAGFlow 图谱绑定每次
提问少一次请求;停用实例时不再把它错误标记为连接失败。
This commit is contained in:
mesalogo
2026-09-13 16:01:12 +08:00
parent ca4a58c762
commit 74a2f187ba
4 changed files with 385 additions and 160 deletions
+5 -45
View File
@@ -1247,15 +1247,14 @@ export class KnowledgeService {
const input = knowledgeRetrieveInputSchema.parse(rawInput)
const library = this.requireLibrary(input.knowledgeBaseId, false)
const settings = input.settings ?? library.retrievalSettings
const binding = this.database.externalStore.listBindings().find(item => item.knowledgeBaseId === library.id)
const binding = this.database.externalStore.getBinding(library.id)
if (binding) {
const response = await this.external.testRetrieval({instanceId:binding.instanceId,remoteKnowledgeBaseId:binding.remoteKnowledgeBaseId,commonConfig:binding.commonConfig,providerConfig:binding.providerConfig,testQuery:input.query}, signal)
const response = await this.external.retrieve({instanceId:binding.instanceId,remoteKnowledgeBaseId:binding.remoteKnowledgeBaseId,commonConfig:binding.commonConfig,providerConfig:binding.providerConfig,query:input.query}, signal)
signal.throwIfAborted()
if (JSON.stringify(this.database.externalStore.listBindings().find(item => item.knowledgeBaseId === library.id)) !== JSON.stringify(binding)) throw new Error('EXTERNAL_KB_CONFIG_CHANGED')
return {
query:input.query,durationMs:response.durationMs,settings,
diagnostics:{external:{provider:binding.provider,instanceId:binding.instanceId,remoteKnowledgeBaseId:binding.remoteKnowledgeBaseId},requestedChannels:[],usedChannels:[],degradedChannels:[],candidateCounts:{},channelDurationMs:{},vectorScannedCount:0,filteredByThresholdCount:0,filteredByBudgetCount:0,rerank:{requested:'none',used:'none',status:'skipped',candidateCount:0,durationMs:0}},
results:response.results.map((item,index)=>({knowledgeBaseId:library.id,documentTitle:item.documentTitle,sourceDisplayName:item.sourceDisplayName,snippet:item.snippet,rank:index+1,channels:[],scores:{fusedScore:0},external:{kind:'external' as const,provider:binding.provider,instanceId:binding.instanceId,remoteKnowledgeBaseId:binding.remoteKnowledgeBaseId,remoteDocumentId:item.remoteDocumentId,remoteChunkId:item.remoteChunkId,providerScore:item.providerScore,providerScores:item.providerScores,location:item.location,sourceUrl:item.sourceUrl}})),
results:response.results.map((item,index)=>({knowledgeBaseId:library.id,documentTitle:item.documentTitle,sourceDisplayName:item.sourceDisplayName,snippet:item.snippet,rank:index+1,channels:[],scores:{fusedScore:0},external:{kind:'external' as const,provider:binding.provider,instanceId:binding.instanceId,remoteKnowledgeBaseId:binding.remoteKnowledgeBaseId,remoteDocumentId:item.remoteDocumentId,remoteChunkId:item.remoteChunkId,providerScore:item.providerScore,providerScores:item.providerScores,location:item.location}})),
context:{characterCount:response.results.reduce((sum,item)=>sum+item.snippet.length,0),truncated:false,groups:[]}
}
}
@@ -1693,7 +1692,7 @@ export class KnowledgeService {
this.requireLibrary(id, false)
)
const preparedEmbedding = libraries.some(
(library) => !this.database.externalStore.listBindings().some(item=>item.knowledgeBaseId===library.id) && library.retrievalSettings.vectorWeight > 0
(library) => !this.database.externalStore.hasBinding(library.id) && library.retrievalSettings.vectorWeight > 0
)
? await this.prepareQueryEmbedding(query, signal)
: undefined
@@ -1869,45 +1868,6 @@ export class KnowledgeService {
})
}
async searchHybridMany(
knowledgeBaseIds: readonly string[],
query: string,
limitPerLibrary = 6,
signal?: AbortSignal
): Promise<
Array<{ knowledgeBaseId: string; result: HybridSearchResult }>
> {
const vector = await this.embedQuery(query, signal)
return knowledgeBaseIds.flatMap((knowledgeBaseId) => {
const library = this.requireLibrary(knowledgeBaseId)
const settings = library.retrievalSettings
return this.database
.hybridSearch({
knowledgeBaseId,
query,
limit: limitPerLibrary,
provider:
vector && this.embeddingProvider
? embeddingStorageProvider(this.embeddingProvider)
: undefined,
model: vector ? this.embeddingProvider?.model : undefined,
vector,
graphEnabled: library.graphEnabled,
minimumVectorSimilarity: settings.minimumVectorSimilarity,
candidateMultiplier: settings.candidateMultiplier,
ftsWeight: settings.ftsWeight,
vectorWeight: settings.vectorWeight,
graphWeight: settings.graphWeight,
signal
})
.map((result) => ({ knowledgeBaseId, result }))
}).sort(
(left, right) =>
right.result.retrieval.score - left.result.retrieval.score ||
left.result.chunk.id.localeCompare(right.result.chunk.id)
).slice(0, limitPerLibrary)
}
private async embedQuery(
query: string,
signal?: AbortSignal
@@ -3863,7 +3823,7 @@ export class KnowledgeService {
}
private requireLibrary(id: string, localOnly = true): KnowledgeBase {
if (localOnly && this.database.externalStore.listBindings().some(item => item.knowledgeBaseId === id)) {
if (localOnly && this.database.externalStore.hasBinding(id)) {
throw new Error('EXTERNAL_KB_READ_ONLY')
}
const library = this.database.getKnowledgeBase(id)