Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions console/frontend/_tests_/knowledge-parameter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import assert from 'node:assert/strict';
import test from 'node:test';

import {
applyRerankId,
clearRerankIdForNonRagflow,
} from '../src/components/workflow/modal/knowledge-rerank.js';

test('applyRerankId trims and stores a configured model ID', () => {
const nodeParam = {};

applyRerankId(nodeParam, 'Ragflow-RAG', ' bge-reranker-v2-m3 ');

assert.equal(nodeParam.rerankId, 'bge-reranker-v2-m3');
});

test('applyRerankId removes an empty model ID from the workflow DSL', () => {
const nodeParam = {
rerankId: 'old-reranker',
};

applyRerankId(nodeParam, 'Ragflow-RAG', ' ');

assert.equal('rerankId' in nodeParam, false);
});

test('applyRerankId removes stale configuration for another RAG strategy', () => {
const nodeParam = {
rerankId: 'old-reranker',
};

applyRerankId(nodeParam, 'CBG-RAG', 'old-reranker');

assert.equal('rerankId' in nodeParam, false);
});

test('RAGFlow knowledge changes preserve the configured rerank model', () => {
const nodeParam = {
rerankId: 'bge-reranker-v2-m3',
};

clearRerankIdForNonRagflow(nodeParam, 'Ragflow-RAG');

assert.equal(nodeParam.rerankId, 'bge-reranker-v2-m3');
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
useAddKnowledgeProps,
} from '@/components/workflow/types';
import { Icons } from '@/components/workflow/icons';
import { clearRerankIdForNonRagflow } from '../knowledge-rerank';

const useAddKnowledge = (): useAddKnowledgeProps => {
const { t } = useTranslation();
Expand Down Expand Up @@ -130,6 +131,7 @@ const useAddKnowledge = (): useAddKnowledgeProps => {
old.data.nodeParam.repoList.splice(findKnowledgeIndex, 1);
}
old.data.nodeParam.ragType = knowledge?.tag;
clearRerankIdForNonRagflow(old.data.nodeParam, knowledge?.tag);
old.data.outputs = generateKnowledgeOutput(knowledge?.tag);
return {
...cloneDeep(old),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
import { Icons } from '@/components/workflow/icons';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { getFixedUrl, getAuthorization } from '@/components/workflow/utils';
import { clearRerankIdForNonRagflow } from '../knowledge-rerank';

function KnowledgePreviewModal(): React.ReactElement {
const knowledgeDetailModalOpen = useFlowsManager(
Expand Down Expand Up @@ -185,6 +186,10 @@ const KnowledgeToolbar = ({
old.data.nodeParam.repoList.splice(findKnowledgeIndex, 1);
}
old.data.nodeParam.ragType = knowledge?.tag;
clearRerankIdForNonRagflow(
old.data.nodeParam,
(knowledge as { tag?: string }).tag
);
old.data.outputs = generateKnowledgeOutput(knowledge?.tag);
return {
...cloneDeep(old),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { Slider, InputNumber, Button } from 'antd';
import type { Node } from 'reactflow';
import { Slider, InputNumber, Input, Button } from 'antd';
import { useTranslation } from 'react-i18next';
import useFlowsManager from '@/components/workflow/store/use-flows-manager';
import { useMemoizedFn } from 'ahooks';
import { cloneDeep } from 'lodash';
import { RepoConfig } from '@/components/workflow/types';
import { applyRerankId, RAGFLOW_RAG_TYPE } from '../knowledge-rerank';

const KnowledgeParameter = (): React.ReactElement => {
const { t } = useTranslation();
Expand All @@ -31,12 +33,14 @@ const KnowledgeParameter = (): React.ReactElement => {
setRepoConfig({
topN: currentNode?.data.nodeParam.topN,
score: currentNode?.data.nodeParam.score,
rerankId: currentNode?.data.nodeParam.rerankId,
});
}, [currentNode]);

const handleParameterChange = useMemoizedFn((fn: (old: unknown) => void) => {
const handleParameterChange = useMemoizedFn((fn: (old: Node) => void) => {
if (!currentNode) return;
autoSaveCurrentFlow();
setNode(currentNode?.id, old => {
setNode(currentNode.id, old => {
fn(old);
return {
...cloneDeep(old),
Expand All @@ -49,13 +53,20 @@ const KnowledgeParameter = (): React.ReactElement => {
handleParameterChange(old => {
old.data.nodeParam.topN = repoConfig?.topN;
old.data.nodeParam.score = repoConfig?.score || 0.2;
applyRerankId(
old.data.nodeParam,
currentNode.data.nodeParam.ragType,
repoConfig?.rerankId
);
});
setKnowledgeParameterModalInfo({
open: false,
nodeId: '',
});
});

const isRagflow = currentNode?.data.nodeParam.ragType === RAGFLOW_RAG_TYPE;

return (
<>
{knowledgeParameterModalInfo?.open
Expand Down Expand Up @@ -126,7 +137,7 @@ const KnowledgeParameter = (): React.ReactElement => {
onChange={(value: unknown) => {
setRepoConfig({
...repoConfig,
score: value,
score: typeof value === 'number' ? value : undefined,
});
}}
precision={2}
Expand All @@ -135,6 +146,33 @@ const KnowledgeParameter = (): React.ReactElement => {
controls={false}
/>
</div>
{isRagflow ? (
<>
<p className="text-second font-medium mt-2.5">
{t('workflow.nodes.parameterModal.rerankModelId')}
</p>
<p className="text-desc mt-1.5">
{t(
'workflow.nodes.parameterModal.rerankModelIdDescription'
)}
</p>
<Input
className="global-input mt-3"
value={repoConfig.rerankId}
placeholder={t(
'workflow.nodes.parameterModal.rerankModelIdPlaceholder'
)}
maxLength={512}
allowClear
onChange={event =>
setRepoConfig({
...repoConfig,
rerankId: event.target.value,
})
}
/>
</>
) : null}
<div className="flex flex-row-reverse gap-3 mt-7">
<Button
type="primary"
Expand Down
26 changes: 26 additions & 0 deletions console/frontend/src/components/workflow/modal/knowledge-rerank.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const RAGFLOW_RAG_TYPE = 'Ragflow-RAG';

export const clearRerankIdForNonRagflow = (
nodeParam: Record<string, unknown>,
ragType: unknown
): void => {
if (ragType !== RAGFLOW_RAG_TYPE) {
delete nodeParam.rerankId;
}
};

export const applyRerankId = (
nodeParam: Record<string, unknown>,
ragType: unknown,
rerankId?: string
): void => {
clearRerankIdForNonRagflow(nodeParam, ragType);
if (ragType !== RAGFLOW_RAG_TYPE) return;

const normalizedRerankId = rerankId?.trim();
if (normalizedRerankId) {
nodeParam.rerankId = normalizedRerankId;
return;
}
delete nodeParam.rerankId;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
export interface RepoConfig {
topN?: number;
score?: number;
rerankId?: string;
}
4 changes: 4 additions & 0 deletions console/frontend/src/locales/en-En/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,10 @@ const translation = {
scoreThreshold: 'Score Threshold',
scoreThresholdDescription:
'Used to set the similarity threshold for text fragment filtering.',
rerankModelId: 'Rerank model ID',
rerankModelIdDescription:
'Optional. Enter a rerank model ID configured in RAGFlow. Leave it empty to keep the current retrieval behavior.',
rerankModelIdPlaceholder: 'Enter a rerank model ID',
},
relatedKnowledgeModal: {
title: 'Select Knowledge Base',
Expand Down
4 changes: 4 additions & 0 deletions console/frontend/src/locales/zh-ZH/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,10 @@ const translation = {
'用于筛选与 用户问题相似度最高的文本片段。系统同时会根据选用模型上下文窗口大小动态调整分段数量。',
scoreThreshold: 'Score 阈值',
scoreThresholdDescription: '用于设置文本片段筛选的相似度阈值。',
rerankModelId: 'Rerank 模型 ID',
rerankModelIdDescription:
'可选。填写 RAGFlow 中已配置的 Rerank 模型 ID;留空时保持当前检索行为。',
rerankModelIdPlaceholder: '请输入 Rerank 模型 ID',
},
relatedKnowledgeModal: {
title: '选择知识库',
Expand Down
27 changes: 15 additions & 12 deletions core/workflow/engine/nodes/knowledge/knowledge_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(
dataset_ids: list[str] = [],
threshold: float = 0.1,
history: list[HistoryItem] = [],
rerank_id: str = "",
):
"""
Initialize knowledge configuration parameters.
Expand All @@ -43,6 +44,7 @@ def __init__(
:param flow_id: Optional flow ID for context
:param doc_ids: Optional list of specific document IDs to search
:param threshold: Minimum similarity threshold for results (default: 0.1)
:param rerank_id: Optional RAGFlow rerank model identifier
"""
self.top_n = top_n
self.rag_type = rag_type
Expand All @@ -54,6 +56,7 @@ def __init__(
self.dataset_ids = dataset_ids
self.threshold = threshold
self.history = history
self.rerank_id = rerank_id


class KnowledgeClient:
Expand Down Expand Up @@ -151,18 +154,18 @@ def payload(self) -> str:
if self.config.rag_type == "Ragflow-RAG" and self.config.dataset_ids:
match["datasetId"] = self.config.dataset_ids

_payload = json.dumps(
{
"query": self.config.query,
"topN": self.config.top_n,
"ragType": self.config.rag_type,
"match": match,
"history": [item.dict() for item in self.config.history],
},
ensure_ascii=True,
)

return _payload
payload = {
"query": self.config.query,
"topN": self.config.top_n,
"ragType": self.config.rag_type,
"match": match,
"history": [item.dict() for item in self.config.history],
}
rerank_id = self.config.rerank_id.strip()
if self.config.rag_type == "Ragflow-RAG" and rerank_id:
payload["ragflow_ext"] = {"rerank_id": rerank_id}

return json.dumps(payload, ensure_ascii=True)

def headers(self) -> dict[str, str]:
return {
Expand Down
2 changes: 2 additions & 0 deletions core/workflow/engine/nodes/knowledge/knowledge_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class KnowledgeNode(BaseLLMNode):
default_factory=list
) # Optional list of specific document IDs to search
datasetIds: list[str] = Field(default_factory=list)
rerankId: str = Field(default="", max_length=512)
score: float = Field(default=0.1) # Minimum similarity threshold for results
enableChatHistoryV2: EnableChatHistoryV2 = Field(
default_factory=EnableChatHistoryV2
Expand Down Expand Up @@ -328,6 +329,7 @@ async def execute(
dataset_ids=repo_and_doc_ids.dataset_ids,
threshold=self.score,
history=history,
rerank_id=self.rerankId,
)
# Perform knowledge base search
search_result = await KnowledgeClient(config=knowledge_config).top_k(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,66 @@ def test_payload_includes_dataset_ids_for_ragflow() -> None:
assert payload["match"]["datasetId"] == ["dataset-1"]


def test_payload_includes_trimmed_rerank_id_for_ragflow() -> None:
config = KnowledgeConfig(
top_n="3",
rag_type="Ragflow-RAG",
repo_id=["repo-1"],
dataset_ids=["dataset-1"],
url="http://knowledge/knowledge/v1/chunk/query",
query="hello",
rerank_id=" bge-reranker-v2-m3 ",
)

payload = json.loads(KnowledgeClient(config=config).payload())

assert payload["ragflow_ext"] == {"rerank_id": "bge-reranker-v2-m3"}


def test_payload_omits_ragflow_ext_when_rerank_id_is_blank() -> None:
config = KnowledgeConfig(
top_n="3",
rag_type="Ragflow-RAG",
repo_id=["repo-1"],
dataset_ids=["dataset-1"],
url="http://knowledge/knowledge/v1/chunk/query",
query="hello",
rerank_id=" ",
)

payload = json.loads(KnowledgeClient(config=config).payload())

assert payload == {
"query": "hello",
"topN": "3",
"ragType": "Ragflow-RAG",
"match": {
"repoId": ["repo-1"],
"docIds": [],
"flowId": "",
"threshold": 0.1,
"datasetId": ["dataset-1"],
},
"history": [],
}


def test_payload_ignores_rerank_id_for_non_ragflow_strategy() -> None:
config = KnowledgeConfig(
top_n="3",
rag_type="AIUI-RAG2",
repo_id=["repo-1"],
url="http://knowledge/knowledge/v1/chunk/query",
query="hello",
rerank_id="bge-reranker-v2-m3",
)

payload = json.loads(KnowledgeClient(config=config).payload())

assert "ragflow_ext" not in payload
assert "datasetId" not in payload["match"]


def test_headers_include_page_managed_ragflow_config() -> None:
config = KnowledgeConfig(
top_n="3",
Expand Down
Loading
Loading