Generate navigation, navigation search (#18096)

### Summary
2 API

POST /api/v1/datasets/{dataset_id}/navigation

GET
/api/v1/datasets/{dataset_id}/navigation/search?q={query}&mode={mode}&top_k={topk}


2 cli

uv run --no-sync python3 admin/client/ragflow_cli.py -h 127.0.0.1 -p
9380 -t user

ragflow> GENERATE NAVIGATION OF DATASET 'frames tree';

ragflow> NAVIGATION SEARCH 'Christie introduced blockchain-based digital
passports' IN DATASET 'frames tree' MODE 'all' topk 20;

(mode: chunk, nav_cluster, nav_doc, navigation_tree, all)
This commit is contained in:
qinling0210
2026-08-11 17:48:24 +08:00
committed by GitHub
parent a399b93143
commit d49af7f218
12 changed files with 1270 additions and 93 deletions

View File

@@ -14,18 +14,19 @@
# limitations under the License.
#
import re
import copy
import json
import re
import time
import copy
from elasticsearch_dsl import UpdateByQuery, Q, Search
from elastic_transport import ConnectionTimeout
from elasticsearch_dsl import Q, Search, UpdateByQuery
from common.constants import PAGERANK_FLD, TAG_FLD
from common.decorator import singleton
from common.doc_store.doc_store_base import MatchTextExpr, OrderByExpr, MatchExpr, MatchDenseExpr, FusionExpr
from common.doc_store.doc_store_base import FusionExpr, MatchDenseExpr, MatchExpr, MatchTextExpr, OrderByExpr
from common.doc_store.es_conn_base import ESConnectionBase
from common.float_utils import get_float
from common.constants import PAGERANK_FLD, TAG_FLD
ATTEMPT_TIME = 2
MAX_RESULT_WINDOW = 10000
@@ -227,7 +228,7 @@ class ESConnection(ESConnectionBase):
elif isinstance(v, str) or isinstance(v, int):
bool_query.filter.append(Q("term", **{k: v}))
else:
raise Exception(f"Condition `{str(k)}={str(v)}` value type is {str(type(v))}, expected to be int, str or list.")
raise Exception(f"Condition `{k!s}={v!s}` value type is {type(v)!s}, expected to be int, str or list.")
s = Search()
vector_similarity_weight = 0.5
@@ -243,7 +244,7 @@ class ESConnection(ESConnectionBase):
vector_similarity_weight = get_float(weights.split(",")[1])
for m in match_expressions:
if isinstance(m, MatchTextExpr):
minimum_should_match = m.extra_options.get("minimum_should_match", 0.0)
minimum_should_match = (m.extra_options or {}).get("minimum_should_match", 0.0)
if isinstance(minimum_should_match, float):
minimum_should_match = str(int(minimum_should_match * 100)) + "%"
bool_query.must.append(Q("query_string", fields=m.fields, type="best_fields", query=m.matching_text, minimum_should_match=minimum_should_match, boost=1))
@@ -254,10 +255,11 @@ class ESConnection(ESConnectionBase):
similarity = 0.0
if "similarity" in m.extra_options:
similarity = m.extra_options["similarity"]
k = min(m.topn, 10000)
s = s.knn(
m.vector_column_name,
m.topn,
m.topn * 2,
k,
min(k * 2, 10000),
query_vector=list(m.embedding_data),
filter=bool_query.to_dict(), # filter=_build_knn_filter_query(bool_query, vector_similarity_weight),
similarity=similarity,
@@ -310,7 +312,7 @@ class ESConnection(ESConnectionBase):
vector_fields = [f for f in (select_fields or []) if f.endswith("_vec")]
if vector_fields:
q["fields"] = vector_fields
self.logger.debug(f"ESConnection.search {str(index_names)} query: " + json.dumps(q))
self.logger.debug(f"ESConnection.search {index_names!s} query: " + json.dumps(q))
for i in range(ATTEMPT_TIME):
try:
@@ -321,7 +323,7 @@ class ESConnection(ESConnectionBase):
res = self._es_search_once(index_names, q, track_total_hits=True)
if str(res.get("timed_out", "")).lower() == "true":
raise Exception("Es Timeout.")
self.logger.debug(f"ESConnection.search {str(index_names)} res: " + str(res))
self.logger.debug(f"ESConnection.search {index_names!s} res: " + str(res))
return res
except ConnectionTimeout:
self.logger.exception("ES request timeout")
@@ -330,9 +332,9 @@ class ESConnection(ESConnectionBase):
except Exception as e:
# Only log debug for NotFoundError(accepted when metadata index doesn't exist)
if "NotFound" in str(e):
self.logger.debug(f"ESConnection.search {str(index_names)} query: " + str(q) + " - " + str(e))
self.logger.debug(f"ESConnection.search {index_names!s} query: " + str(q) + " - " + str(e))
else:
self.logger.exception(f"ESConnection.search {str(index_names)} query: " + str(q) + str(e))
self.logger.exception(f"ESConnection.search {index_names!s} query: " + str(q) + str(e))
raise e
self.logger.error(f"ESConnection.search timeout for {ATTEMPT_TIME} times!")
@@ -443,7 +445,7 @@ class ESConnection(ESConnectionBase):
elif isinstance(v, str) or isinstance(v, int):
bool_query.filter.append(Q("term", **{k: v}))
else:
raise Exception(f"Condition `{str(k)}={str(v)}` value type is {str(type(v))}, expected to be int, str or list.")
raise Exception(f"Condition `{k!s}={v!s}` value type is {type(v)!s}, expected to be int, str or list.")
scripts = []
params = {}
for k, v in new_value.items():
@@ -473,7 +475,7 @@ class ESConnection(ESConnectionBase):
scripts.append(f"ctx._source.{k}=params.pp_{k};")
params[f"pp_{k}"] = json.dumps(v, ensure_ascii=False)
else:
raise Exception(f"newValue `{str(k)}={str(v)}` value type is {str(type(v))}, expected to be int, str.")
raise Exception(f"newValue `{k!s}={v!s}` value type is {type(v)!s}, expected to be int, str.")
ubq = UpdateByQuery(index=index_name).using(self.es).query(bool_query)
ubq = ubq.script(source="".join(scripts), params=params)
ubq = ubq.params(refresh=True)