mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-02 13:57:30 +08:00
fix: stabilize knowledge compilation navigation updates (#17345)
This commit is contained in:
@@ -1135,6 +1135,9 @@ async def rerun_agent(tenant_id):
|
|||||||
if 0 < doc["progress"] < 1:
|
if 0 < doc["progress"] < 1:
|
||||||
return get_data_error_result(message=f"`{doc['name']}` is processing...")
|
return get_data_error_result(message=f"`{doc['name']}` is processing...")
|
||||||
|
|
||||||
|
from rag.advanced_rag.knowlege_compile.dataset_nav import remove_dataset_nav_doc_sync
|
||||||
|
|
||||||
|
remove_dataset_nav_doc_sync(tenant_id, doc["kb_id"], doc["id"])
|
||||||
if settings.docStoreConn.index_exist(search.index_name(tenant_id), doc["kb_id"]):
|
if settings.docStoreConn.index_exist(search.index_name(tenant_id), doc["kb_id"]):
|
||||||
settings.docStoreConn.delete({"doc_id": doc["id"]}, search.index_name(tenant_id), doc["kb_id"])
|
settings.docStoreConn.delete({"doc_id": doc["id"]}, search.index_name(tenant_id), doc["kb_id"])
|
||||||
doc["progress_msg"] = ""
|
doc["progress_msg"] = ""
|
||||||
|
|||||||
@@ -1470,6 +1470,9 @@ def _run_sync(user_id: str, req):
|
|||||||
DocumentService.update_by_id(doc_id, info)
|
DocumentService.update_by_id(doc_id, info)
|
||||||
if req.get("delete", False):
|
if req.get("delete", False):
|
||||||
TaskService.filter_delete([Task.doc_id == doc_id])
|
TaskService.filter_delete([Task.doc_id == doc_id])
|
||||||
|
from rag.advanced_rag.knowlege_compile.dataset_nav import remove_dataset_nav_doc_sync
|
||||||
|
|
||||||
|
remove_dataset_nav_doc_sync(doc_tenant_id, doc.kb_id, doc.id)
|
||||||
if settings.docStoreConn.index_exist(search.index_name(doc_tenant_id), doc.kb_id):
|
if settings.docStoreConn.index_exist(search.index_name(doc_tenant_id), doc.kb_id):
|
||||||
settings.docStoreConn.delete({"doc_id": doc_id}, search.index_name(doc_tenant_id), doc.kb_id)
|
settings.docStoreConn.delete({"doc_id": doc_id}, search.index_name(doc_tenant_id), doc.kb_id)
|
||||||
|
|
||||||
@@ -1580,6 +1583,9 @@ async def parse_documents(tenant_id, dataset_id):
|
|||||||
|
|
||||||
DocumentService.update_by_id(doc_id, info)
|
DocumentService.update_by_id(doc_id, info)
|
||||||
TaskService.filter_delete([Task.doc_id == doc_id])
|
TaskService.filter_delete([Task.doc_id == doc_id])
|
||||||
|
from rag.advanced_rag.knowlege_compile.dataset_nav import remove_dataset_nav_doc_sync
|
||||||
|
|
||||||
|
remove_dataset_nav_doc_sync(tenant_id, doc.kb_id, doc.id)
|
||||||
if settings.docStoreConn.index_exist(search.index_name(tenant_id), doc.kb_id):
|
if settings.docStoreConn.index_exist(search.index_name(tenant_id), doc.kb_id):
|
||||||
settings.docStoreConn.delete({"doc_id": doc_id}, search.index_name(tenant_id), doc.kb_id)
|
settings.docStoreConn.delete({"doc_id": doc_id}, search.index_name(tenant_id), doc.kb_id)
|
||||||
|
|
||||||
|
|||||||
@@ -2452,7 +2452,10 @@ def _nav_item(row: dict) -> dict:
|
|||||||
payload = json.loads(row.get("content_with_weight") or "{}")
|
payload = json.loads(row.get("content_with_weight") or "{}")
|
||||||
except Exception:
|
except Exception:
|
||||||
payload = {}
|
payload = {}
|
||||||
is_cluster = (row.get("type_kwd") or payload.get("type")) == "nav_cluster"
|
row_type = row.get("type_kwd")
|
||||||
|
if isinstance(row_type, (list, tuple, set)):
|
||||||
|
row_type = next(iter(row_type), None)
|
||||||
|
is_cluster = (row_type or payload.get("type")) == "nav_cluster"
|
||||||
return {
|
return {
|
||||||
"name": row.get("name") or "",
|
"name": row.get("name") or "",
|
||||||
"description": payload.get("description") or "",
|
"description": payload.get("description") or "",
|
||||||
@@ -2551,7 +2554,8 @@ async def delete_nav(dataset_id: str, tenant_id: str):
|
|||||||
index_nm, _ = pack
|
index_nm, _ = pack
|
||||||
|
|
||||||
try:
|
try:
|
||||||
deleted = settings.docStoreConn.delete(
|
deleted = await thread_pool_exec(
|
||||||
|
settings.docStoreConn.delete,
|
||||||
{"compile_kwd": [_NAV_COMPILE_KWD]},
|
{"compile_kwd": [_NAV_COMPILE_KWD]},
|
||||||
index_nm,
|
index_nm,
|
||||||
dataset_id,
|
dataset_id,
|
||||||
@@ -2592,16 +2596,17 @@ async def delete_nav_node(dataset_id: str, tenant_id: str, name: str):
|
|||||||
if not frontier:
|
if not frontier:
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
res = settings.docStoreConn.search(
|
res = await thread_pool_exec(
|
||||||
select_fields=["name"],
|
settings.docStoreConn.search,
|
||||||
highlight_fields=[],
|
["name"],
|
||||||
condition={"compile_kwd": [_NAV_COMPILE_KWD], "parent_kwd": frontier},
|
[],
|
||||||
match_expressions=[],
|
{"compile_kwd": [_NAV_COMPILE_KWD], "parent_kwd": frontier},
|
||||||
order_by=OrderByExpr(),
|
[],
|
||||||
offset=0,
|
OrderByExpr(),
|
||||||
limit=10000,
|
0,
|
||||||
index_names=index_nm,
|
10000,
|
||||||
knowledgebase_ids=[dataset_id],
|
index_nm,
|
||||||
|
[dataset_id],
|
||||||
)
|
)
|
||||||
rows = settings.docStoreConn.get_fields(res, ["name"]) or {}
|
rows = settings.docStoreConn.get_fields(res, ["name"]) or {}
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -2616,7 +2621,8 @@ async def delete_nav_node(dataset_id: str, tenant_id: str, name: str):
|
|||||||
frontier = nxt
|
frontier = nxt
|
||||||
|
|
||||||
try:
|
try:
|
||||||
deleted = settings.docStoreConn.delete(
|
deleted = await thread_pool_exec(
|
||||||
|
settings.docStoreConn.delete,
|
||||||
{"compile_kwd": [_NAV_COMPILE_KWD], "name": list(names)},
|
{"compile_kwd": [_NAV_COMPILE_KWD], "name": list(names)},
|
||||||
index_nm,
|
index_nm,
|
||||||
dataset_id,
|
dataset_id,
|
||||||
|
|||||||
@@ -492,6 +492,20 @@ class DocumentService(CommonService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"Failed to delete thumbnail for document {doc.id}: {e}")
|
logging.warning(f"Failed to delete thumbnail for document {doc.id}: {e}")
|
||||||
|
|
||||||
|
# Prune this doc's line from the KB's tree-kind navigation before the
|
||||||
|
# broad doc_id delete below removes the nav_doc row needed to locate
|
||||||
|
# and update its parent cluster.
|
||||||
|
try:
|
||||||
|
from rag.advanced_rag.knowlege_compile.dataset_nav import (
|
||||||
|
remove_dataset_nav_doc_sync,
|
||||||
|
)
|
||||||
|
|
||||||
|
remove_dataset_nav_doc_sync(tenant_id, doc.kb_id, doc.id)
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(
|
||||||
|
f"Failed to prune dataset_nav for document {doc.id}: {e}",
|
||||||
|
)
|
||||||
|
|
||||||
# Delete chunks from doc store - this is critical, log errors
|
# Delete chunks from doc store - this is critical, log errors
|
||||||
try:
|
try:
|
||||||
settings.docStoreConn.delete({"doc_id": doc.id}, chunk_index_name, doc.kb_id)
|
settings.docStoreConn.delete({"doc_id": doc.id}, chunk_index_name, doc.kb_id)
|
||||||
@@ -507,20 +521,6 @@ class DocumentService(CommonService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"Failed to clean up artifact products for document {doc.id}: {e}")
|
logging.warning(f"Failed to clean up artifact products for document {doc.id}: {e}")
|
||||||
|
|
||||||
# Prune this doc's line from the KB's tree-kind navigation
|
|
||||||
# markdown (best-effort — the markdown is a downstream artifact,
|
|
||||||
# and failure here must not block the document delete).
|
|
||||||
try:
|
|
||||||
from rag.advanced_rag.knowlege_compile.dataset_nav import (
|
|
||||||
remove_dataset_nav_doc_sync,
|
|
||||||
)
|
|
||||||
|
|
||||||
remove_dataset_nav_doc_sync(tenant_id, doc.kb_id, doc.id)
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(
|
|
||||||
f"Failed to prune dataset_nav for document {doc.id}: {e}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete document metadata (non-critical, log and continue)
|
# Delete document metadata (non-critical, log and continue)
|
||||||
try:
|
try:
|
||||||
DocMetadataService.delete_document_metadata(doc.id, doc.kb_id, tenant_id)
|
DocMetadataService.delete_document_metadata(doc.id, doc.kb_id, tenant_id)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from rag.nlp import is_english
|
|||||||
from common import settings
|
from common import settings
|
||||||
from common.doc_store.doc_store_base import DocStoreConnection, MatchExpr, OrderByExpr
|
from common.doc_store.doc_store_base import DocStoreConnection, MatchExpr, OrderByExpr
|
||||||
|
|
||||||
|
|
||||||
# Concurrent CREATE/DROP TABLE on the same Infinity instance can race on
|
# Concurrent CREATE/DROP TABLE on the same Infinity instance can race on
|
||||||
# Infinity's RocksDB-backed catalog counters (e.g. ``db|1|next_table_id``).
|
# Infinity's RocksDB-backed catalog counters (e.g. ``db|1|next_table_id``).
|
||||||
# When two writers touch the counter at the same instant, Infinity surfaces
|
# When two writers touch the counter at the same instant, Infinity surfaces
|
||||||
@@ -321,6 +320,17 @@ class InfinityConnectionBase(DocStoreConnection):
|
|||||||
json_conditions.append(f"json_contains({k}, '{literal}')")
|
json_conditions.append(f"json_contains({k}, '{literal}')")
|
||||||
if json_conditions:
|
if json_conditions:
|
||||||
cond.append("(" + " or ".join(json_conditions) + ")")
|
cond.append("(" + " or ".join(json_conditions) + ")")
|
||||||
|
elif k in {"compile_kwd", "type_kwd", "parent_kwd"}:
|
||||||
|
values = v if isinstance(v, list) else [v]
|
||||||
|
exact_conditions = []
|
||||||
|
for item in values:
|
||||||
|
if isinstance(item, str):
|
||||||
|
item = item.replace("'", "''")
|
||||||
|
exact_conditions.append(f"{k}='{item}'")
|
||||||
|
else:
|
||||||
|
exact_conditions.append(f"{k}={item}")
|
||||||
|
if exact_conditions:
|
||||||
|
cond.append("(" + " or ".join(exact_conditions) + ")")
|
||||||
elif self.field_keyword(k):
|
elif self.field_keyword(k):
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
inCond = list()
|
inCond = list()
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ async def _store_knn(
|
|||||||
"content_with_weight",
|
"content_with_weight",
|
||||||
"name",
|
"name",
|
||||||
"doc_id",
|
"doc_id",
|
||||||
|
"compile_kwd",
|
||||||
"type_kwd",
|
"type_kwd",
|
||||||
"parent_kwd",
|
"parent_kwd",
|
||||||
"depth_int",
|
"depth_int",
|
||||||
@@ -213,7 +214,13 @@ async def _store_knn(
|
|||||||
[kb_id],
|
[kb_id],
|
||||||
)
|
)
|
||||||
results = settings.docStoreConn.get_fields(res, fields) if res else {}
|
results = settings.docStoreConn.get_fields(res, fields) if res else {}
|
||||||
return list(results.values())
|
rows = list(results.values())
|
||||||
|
if filter_condition and any(not _matches_condition(row, filter_condition) for row in rows):
|
||||||
|
scanned = await _store_search(tenant_id, kb_id, filter_condition, fields, limit=10000)
|
||||||
|
rows = [row for row in scanned if _vector_len(row.get(vf)) == vec_dim and _matches_condition(row, filter_condition)]
|
||||||
|
rows.sort(key=lambda row: _cosine_sim(vec, row.get(vf)), reverse=True)
|
||||||
|
rows = rows[:top_k]
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
async def _store_upsert(tenant_id: str, kb_id: str, doc: dict) -> None:
|
async def _store_upsert(tenant_id: str, kb_id: str, doc: dict) -> None:
|
||||||
@@ -388,6 +395,19 @@ def _fine_tokenize(text: str) -> str:
|
|||||||
return rag_tokenizer.fine_grained_tokenize(text)
|
return rag_tokenizer.fine_grained_tokenize(text)
|
||||||
|
|
||||||
|
|
||||||
|
def _matches_condition(row: dict, condition: dict) -> bool:
|
||||||
|
"""Check the simple equality filters used by dataset navigation."""
|
||||||
|
for field, expected in condition.items():
|
||||||
|
if not expected or field == "kb_id":
|
||||||
|
continue
|
||||||
|
actual = row.get(field)
|
||||||
|
actual_values = actual if isinstance(actual, (list, tuple, set)) else [actual]
|
||||||
|
expected_values = expected if isinstance(expected, (list, tuple, set)) else [expected]
|
||||||
|
if not any(str(value) == str(item) for value in actual_values for item in expected_values):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Incremental clustering core
|
# Incremental clustering core
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -426,6 +446,7 @@ async def _find_best_cluster(
|
|||||||
# compute actual similarity to root
|
# compute actual similarity to root
|
||||||
stored = best.get(_vec_field(vec_dim))
|
stored = best.get(_vec_field(vec_dim))
|
||||||
sim = _cosine_sim(doc_embedding, stored)
|
sim = _cosine_sim(doc_embedding, stored)
|
||||||
|
visited_names = {best_name}
|
||||||
|
|
||||||
# Step 2: recursively descend into children
|
# Step 2: recursively descend into children
|
||||||
while sim >= _RECURSE_THRESHOLD:
|
while sim >= _RECURSE_THRESHOLD:
|
||||||
@@ -439,14 +460,18 @@ async def _find_best_cluster(
|
|||||||
if not children:
|
if not children:
|
||||||
break
|
break
|
||||||
child = children[0]
|
child = children[0]
|
||||||
|
child_name = child.get("name", "")
|
||||||
|
if not child_name or child_name in visited_names:
|
||||||
|
break
|
||||||
stored = child.get(_vec_field(vec_dim))
|
stored = child.get(_vec_field(vec_dim))
|
||||||
child_sim = _cosine_sim(doc_embedding, stored)
|
child_sim = _cosine_sim(doc_embedding, stored)
|
||||||
if child_sim < _RECURSE_THRESHOLD:
|
if child_sim < _RECURSE_THRESHOLD:
|
||||||
break
|
break
|
||||||
best_name = child.get("name", best_name)
|
best_name = child_name
|
||||||
best_parent = best.get("parent_kwd", best_parent)
|
best_parent = best.get("parent_kwd", best_parent)
|
||||||
sim = child_sim
|
sim = child_sim
|
||||||
best = child
|
best = child
|
||||||
|
visited_names.add(best_name)
|
||||||
|
|
||||||
return best_name, best_parent, sim
|
return best_name, best_parent, sim
|
||||||
|
|
||||||
@@ -568,15 +593,9 @@ async def upsert_dataset_nav_doc(
|
|||||||
logging.info("dataset_nav: skipping doc=%s (kb=%s) — no summary", doc_id, kb_id)
|
logging.info("dataset_nav: skipping doc=%s (kb=%s) — no summary", doc_id, kb_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2. Check if this doc already has a nav_doc row
|
# 2. Embed doc summary before taking the KB lock. The result is
|
||||||
existing_doc = await _store_get(tenant_id, kb_id, _nav_doc_id(doc_id))
|
# independent of the nav tree; all tree reads, deletes, and writes below
|
||||||
if existing_doc:
|
# are serialized by the same lock.
|
||||||
old_payload = json.loads(existing_doc.get("content_with_weight") or "{}")
|
|
||||||
if old_payload.get("description") == summary:
|
|
||||||
logging.info("dataset_nav: doc=%s unchanged, skipping", doc_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 3. Embed doc summary
|
|
||||||
doc_embedding = await _embed(embd_mdl, summary) if embd_mdl else []
|
doc_embedding = await _embed(embd_mdl, summary) if embd_mdl else []
|
||||||
vec_dim = len(doc_embedding)
|
vec_dim = len(doc_embedding)
|
||||||
|
|
||||||
@@ -592,8 +611,18 @@ async def upsert_dataset_nav_doc(
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 3. Check and replace the existing nav_doc while holding the same
|
||||||
|
# lock as placement. Otherwise concurrent updates can both observe
|
||||||
|
# the old row and race while deleting/rebuilding its parent cluster.
|
||||||
|
existing_doc = await _store_get(tenant_id, kb_id, _nav_doc_id(doc_id))
|
||||||
|
if existing_doc:
|
||||||
|
old_payload = json.loads(existing_doc.get("content_with_weight") or "{}")
|
||||||
|
if old_payload.get("description") == summary:
|
||||||
|
return
|
||||||
|
await _remove_dataset_nav_doc_locked(tenant_id, kb_id, doc_id)
|
||||||
|
|
||||||
# 4. Layered KNN search for nearest cluster
|
# 4. Layered KNN search for nearest cluster
|
||||||
if doc_embedding:
|
if _vector_len(doc_embedding) > 0:
|
||||||
best_name, best_parent, sim = await _find_best_cluster(
|
best_name, best_parent, sim = await _find_best_cluster(
|
||||||
tenant_id,
|
tenant_id,
|
||||||
kb_id,
|
kb_id,
|
||||||
@@ -726,6 +755,45 @@ async def upsert_dataset_nav_doc(
|
|||||||
logging.exception("dataset_nav: lock release failed for kb=%s", kb_id)
|
logging.exception("dataset_nav: lock release failed for kb=%s", kb_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def _remove_dataset_nav_doc_locked(
|
||||||
|
tenant_id: str,
|
||||||
|
kb_id: str,
|
||||||
|
doc_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Remove a document nav row; the caller must hold the KB nav lock."""
|
||||||
|
# 1. Find and delete the nav_doc row
|
||||||
|
doc_row_id = _nav_doc_id(doc_id)
|
||||||
|
doc_row = await _store_get(tenant_id, kb_id, doc_row_id)
|
||||||
|
if not doc_row:
|
||||||
|
return
|
||||||
|
parent_name = doc_row.get("parent_kwd", "")
|
||||||
|
await _store_delete(tenant_id, kb_id, doc_row_id)
|
||||||
|
|
||||||
|
# 2. Remove doc_id from the parent cluster's doc_ids_kwd
|
||||||
|
if parent_name and parent_name != "root":
|
||||||
|
cluster_id = _nav_cluster_id(kb_id, parent_name)
|
||||||
|
cluster_row = await _store_get(tenant_id, kb_id, cluster_id)
|
||||||
|
if cluster_row:
|
||||||
|
doc_ids = cluster_row.get("doc_ids_kwd") or []
|
||||||
|
if doc_id in doc_ids:
|
||||||
|
doc_ids.remove(doc_id)
|
||||||
|
if not doc_ids:
|
||||||
|
# Cluster is empty — delete it
|
||||||
|
await _store_delete(tenant_id, kb_id, cluster_id)
|
||||||
|
# Recurse: check grandparent
|
||||||
|
grandparent = cluster_row.get("parent_kwd", "")
|
||||||
|
if grandparent and grandparent != "root":
|
||||||
|
await _cleanup_empty_cluster(
|
||||||
|
tenant_id,
|
||||||
|
kb_id,
|
||||||
|
grandparent,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cluster_row["doc_ids_kwd"] = doc_ids
|
||||||
|
cluster_row["doc_count_int"] = len(doc_ids)
|
||||||
|
await _store_upsert(tenant_id, kb_id, cluster_row)
|
||||||
|
|
||||||
|
|
||||||
async def remove_dataset_nav_doc(
|
async def remove_dataset_nav_doc(
|
||||||
tenant_id: str,
|
tenant_id: str,
|
||||||
kb_id: str,
|
kb_id: str,
|
||||||
@@ -751,37 +819,7 @@ async def remove_dataset_nav_doc(
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. Find and delete the nav_doc row
|
await _remove_dataset_nav_doc_locked(tenant_id, kb_id, doc_id)
|
||||||
doc_row_id = _nav_doc_id(doc_id)
|
|
||||||
doc_row = await _store_get(tenant_id, kb_id, doc_row_id)
|
|
||||||
if not doc_row:
|
|
||||||
return
|
|
||||||
parent_name = doc_row.get("parent_kwd", "")
|
|
||||||
await _store_delete(tenant_id, kb_id, doc_row_id)
|
|
||||||
|
|
||||||
# 2. Remove doc_id from the parent cluster's doc_ids_kwd
|
|
||||||
if parent_name and parent_name != "root":
|
|
||||||
cluster_id = _nav_cluster_id(kb_id, parent_name)
|
|
||||||
cluster_row = await _store_get(tenant_id, kb_id, cluster_id)
|
|
||||||
if cluster_row:
|
|
||||||
doc_ids = cluster_row.get("doc_ids_kwd") or []
|
|
||||||
if doc_id in doc_ids:
|
|
||||||
doc_ids.remove(doc_id)
|
|
||||||
if not doc_ids:
|
|
||||||
# Cluster is empty — delete it
|
|
||||||
await _store_delete(tenant_id, kb_id, cluster_id)
|
|
||||||
# Recurse: check grandparent
|
|
||||||
grandparent = cluster_row.get("parent_kwd", "")
|
|
||||||
if grandparent and grandparent != "root":
|
|
||||||
await _cleanup_empty_cluster(
|
|
||||||
tenant_id,
|
|
||||||
kb_id,
|
|
||||||
grandparent,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
cluster_row["doc_ids_kwd"] = doc_ids
|
|
||||||
cluster_row["doc_count_int"] = len(doc_ids)
|
|
||||||
await _store_upsert(tenant_id, kb_id, cluster_row)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
"dataset_nav: remove failed for kb=%s doc=%s",
|
"dataset_nav: remove failed for kb=%s doc=%s",
|
||||||
|
|||||||
@@ -197,13 +197,27 @@ async def _upsert_dataset_nav_from_page_index(
|
|||||||
if cancel_check():
|
if cancel_check():
|
||||||
raise TaskCanceledException("Task was cancelled before dataset navigation update")
|
raise TaskCanceledException("Task was cancelled before dataset navigation update")
|
||||||
try:
|
try:
|
||||||
|
# PageIndex rows use their preserved template kind as the
|
||||||
|
# compile keyword. Older rows were written as ``timeline``
|
||||||
|
# before compilation kinds were kept distinct, so fall back to
|
||||||
|
# the legacy keyword when rebuilding an existing graph.
|
||||||
graph = await rebuild_structure_graph_json(
|
graph = await rebuild_structure_graph_json(
|
||||||
tenant_id,
|
tenant_id,
|
||||||
kb_id,
|
kb_id,
|
||||||
doc_id,
|
doc_id,
|
||||||
"timeline",
|
"page_index",
|
||||||
compilation_template_id=template_id,
|
compilation_template_id=template_id,
|
||||||
)
|
)
|
||||||
|
summary = _page_index_graph_summary(graph)
|
||||||
|
if not summary:
|
||||||
|
graph = await rebuild_structure_graph_json(
|
||||||
|
tenant_id,
|
||||||
|
kb_id,
|
||||||
|
doc_id,
|
||||||
|
"timeline",
|
||||||
|
compilation_template_id=template_id,
|
||||||
|
)
|
||||||
|
summary = _page_index_graph_summary(graph)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
"page_index: failed to rebuild graph summary for dataset_nav doc %s template %s",
|
"page_index: failed to rebuild graph summary for dataset_nav doc %s template %s",
|
||||||
@@ -212,7 +226,6 @@ async def _upsert_dataset_nav_from_page_index(
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
summary = _page_index_graph_summary(graph)
|
|
||||||
if summary:
|
if summary:
|
||||||
summaries.append(summary)
|
summaries.append(summary)
|
||||||
chat_mdl = chat_mdl or chat_mdl_by_tid.get(template_id)
|
chat_mdl = chat_mdl or chat_mdl_by_tid.get(template_id)
|
||||||
|
|||||||
@@ -163,7 +163,14 @@ class Compiler(ProcessBase, LLM):
|
|||||||
try:
|
try:
|
||||||
from rag.advanced_rag.knowlege_compile.dataset_nav import upsert_dataset_nav_doc
|
from rag.advanced_rag.knowlege_compile.dataset_nav import upsert_dataset_nav_doc
|
||||||
|
|
||||||
await upsert_dataset_nav_doc(tenant_id, kb_id, doc_id, tree)
|
await upsert_dataset_nav_doc(
|
||||||
|
tenant_id,
|
||||||
|
kb_id,
|
||||||
|
doc_id,
|
||||||
|
tree,
|
||||||
|
embd_mdl=embedding_model,
|
||||||
|
chat_mdl=chat_mdl_by_tid[template_id],
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Compiler: tree-template %s dataset navigation upsert failed for doc %s", template_id, doc_id)
|
logging.exception("Compiler: tree-template %s dataset navigation upsert failed for doc %s", template_id, doc_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1009,6 +1009,8 @@ async def run_tree_templates(
|
|||||||
ctx.kb_id,
|
ctx.kb_id,
|
||||||
doc_id,
|
doc_id,
|
||||||
tree,
|
tree,
|
||||||
|
embd_mdl=embedding_model,
|
||||||
|
chat_mdl=chat_mdl_by_tid[template_id],
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
|
|||||||
@@ -649,6 +649,22 @@ class InfinityConnection(InfinityConnectionBase):
|
|||||||
if k in new_value:
|
if k in new_value:
|
||||||
del new_value[k]
|
del new_value[k]
|
||||||
|
|
||||||
|
# The Infinity Python client inspects value[0] for list values
|
||||||
|
# while building an update expression. An empty list therefore
|
||||||
|
# raises IndexError before the request reaches Infinity. Keep
|
||||||
|
# JSON and keyword-list columns clearable, but do not send empty
|
||||||
|
# values for other columns (for example, an empty vector returned
|
||||||
|
# by a partial row read).
|
||||||
|
for k, v in list(new_value.items()):
|
||||||
|
if not isinstance(v, list) or v:
|
||||||
|
continue
|
||||||
|
if k in _JSON_LIST_FIELDS:
|
||||||
|
new_value[k] = json.dumps([], ensure_ascii=False)
|
||||||
|
elif self.field_keyword(k):
|
||||||
|
new_value[k] = ""
|
||||||
|
else:
|
||||||
|
del new_value[k]
|
||||||
|
|
||||||
remove_opt = {} # "[k,new_value]": [id_to_update, ...]
|
remove_opt = {} # "[k,new_value]": [id_to_update, ...]
|
||||||
if removeValue:
|
if removeValue:
|
||||||
col_to_remove = list(removeValue.keys())
|
col_to_remove = list(removeValue.keys())
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface TreeDataItem {
|
|||||||
selectedIcon?: any;
|
selectedIcon?: any;
|
||||||
openIcon?: any;
|
openIcon?: any;
|
||||||
children?: TreeDataItem[];
|
children?: TreeDataItem[];
|
||||||
|
hasChildren?: boolean;
|
||||||
actions?: React.ReactNode;
|
actions?: React.ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
@@ -142,7 +143,8 @@ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
|||||||
<ul>
|
<ul>
|
||||||
{data.map((item) => (
|
{data.map((item) => (
|
||||||
<li key={item.id}>
|
<li key={item.id}>
|
||||||
{item.children && item.children.length > 0 ? (
|
{item.hasChildren ||
|
||||||
|
(item.children && item.children.length > 0) ? (
|
||||||
<TreeNode
|
<TreeNode
|
||||||
item={item}
|
item={item}
|
||||||
selectedItemId={selectedItemId}
|
selectedItemId={selectedItemId}
|
||||||
@@ -216,7 +218,7 @@ const TreeNode = ({
|
|||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="ml-4 pl-1 border-l">
|
<AccordionContent className="ml-4 pl-1 border-l">
|
||||||
<TreeItem
|
<TreeItem
|
||||||
data={item.children ? item.children : item}
|
data={item.children ?? []}
|
||||||
selectedItemId={selectedItemId}
|
selectedItemId={selectedItemId}
|
||||||
handleSelectChange={handleSelectChange}
|
handleSelectChange={handleSelectChange}
|
||||||
expandedItemIds={expandedItemIds}
|
expandedItemIds={expandedItemIds}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function buildNavTreeData(
|
|||||||
const item: TreeDataItem = {
|
const item: TreeDataItem = {
|
||||||
id: node.name,
|
id: node.name,
|
||||||
name: node.name,
|
name: node.name,
|
||||||
|
hasChildren: node.has_children,
|
||||||
actions: getActions?.(node, null),
|
actions: getActions?.(node, null),
|
||||||
onClick: () => onParentClick(node),
|
onClick: () => onParentClick(node),
|
||||||
};
|
};
|
||||||
@@ -39,6 +40,7 @@ export function buildNavTreeData(
|
|||||||
item.children = children.map((child) => ({
|
item.children = children.map((child) => ({
|
||||||
id: `${node.name}/${child.name}`,
|
id: `${node.name}/${child.name}`,
|
||||||
name: child.name,
|
name: child.name,
|
||||||
|
hasChildren: child.has_children,
|
||||||
actions: getActions?.(child, node.name),
|
actions: getActions?.(child, node.name),
|
||||||
onClick: () => onChildClick(child, node.name),
|
onClick: () => onChildClick(child, node.name),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user