feat(ragflow): support request-scoped retrieval filters - #2633
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
CI rerun report for the unchanged commit
In each completed failure, the test body had no assertion failure. JUnit failed while closing the extension context because its temporary directory could not be deleted ( The focused RAGFlow suite still passes locally: 97 tests, 0 failures, 0 errors. One all-green run followed by two unchanged-SHA runs whose cleanup error moved between Harness tests and operating systems is strong evidence of a repository-level flaky Harness cleanup race rather than a failure caused by this PR's three RAGFlow files. No code change has been made in response to these CI runs. |
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR adds request-scoped datasetIds and metadataCondition overloads to RAGFlowKnowledge and RAGFlowClient, solving the problem of having to rebuild a knowledge instance to change retrieval filters between calls. The implementation is clean: old methods delegate to new ones, defensive copies are made before the deferred Mono.fromCallable execution, and fallback to config values works correctly. Test coverage is solid with both dynamic-filter and config-fallback scenarios. One subtle behavior change in how dataset_ids is serialized should be noted.
| // Required: dataset_ids (array) | ||
| requestBody.put("dataset_ids", config.getDatasetIds()); | ||
| // Required unless document_ids is provided: dataset_ids (array) | ||
| if (!effectiveDatasetIds.isEmpty()) { |
There was a problem hiding this comment.
[minor] Subtle behavior change: the old code unconditionally put dataset_ids in the request body (even when config.getDatasetIds() was null or empty), while the new code omits the field entirely when the effective list is empty. This is actually more correct per the RAGFlow API contract ("Required unless document_ids is provided"), but the PR description states "keeps the existing APIs and configuration validation unchanged." Consider mentioning this in the PR description for reviewer awareness, and confirming that the RAGFlow server handles a missing dataset_ids field the same way it handled "dataset_ids": null.
| Map<String, Object> effectiveMetadataCondition = | ||
| metadataCondition != null && !metadataCondition.isEmpty() | ||
| ? new HashMap<>(metadataCondition) | ||
| : copyMap(config.getMetadataCondition()); |
There was a problem hiding this comment.
[nit] The shallow copy via new HashMap<>(metadataCondition) protects against top-level mutations (put/remove), but nested mutable values inside the map are still shared between the caller and the deferred request. The Javadoc states "Parameter values are copied … so that later caller mutations cannot affect the asynchronous request," which is slightly overreaching. In practice this is unlikely to matter since metadata condition values are typically primitives/strings, but consider either softening the Javadoc claim (e.g. "top-level copies") or noting the limitation.
| Map<String, Object> metadataCondition) { | ||
|
|
||
| List<String> effectiveDatasetIds = | ||
| datasetIds != null && !datasetIds.isEmpty() |
There was a problem hiding this comment.
[praise] Good defensive-copying pattern. Copying datasetIds and metadataCondition eagerly (before Mono.fromCallable) ensures that mutations on the caller's collections after this method returns cannot affect the in-flight async request. Clean implementation.
| void testRetrieveWithDynamicFiltersAndConfigFallback() throws Exception { | ||
| mockWebServer.enqueue(createSuccessResponse()); | ||
| mockWebServer.enqueue(createSuccessResponse()); | ||
| mockWebServer.enqueue(createSuccessResponse()); |
There was a problem hiding this comment.
[praise] Good test coverage: verifies two consecutive requests with different dynamic filters produce distinct request bodies, and that empty/null parameters correctly fall back to config values. This gives confidence that request isolation and config fallback both work as intended.
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR adds request-scoped datasetIds and metadataCondition overloads to RAGFlowKnowledge and RAGFlowClient, solving the problem of having to rebuild a knowledge instance to change retrieval filters between calls. The implementation is clean: old methods delegate to new ones, defensive copies are made before the deferred Mono.fromCallable execution, and fallback to config values works correctly. Test coverage is solid with both dynamic-filter and config-fallback scenarios. One subtle behavior change in how dataset_ids is serialized should be noted.
| // Required: dataset_ids (array) | ||
| requestBody.put("dataset_ids", config.getDatasetIds()); | ||
| // Required unless document_ids is provided: dataset_ids (array) | ||
| if (!effectiveDatasetIds.isEmpty()) { |
There was a problem hiding this comment.
[minor] Subtle behavior change: the old code unconditionally put dataset_ids in the request body (even when config.getDatasetIds() was null or empty), while the new code omits the field entirely when the effective list is empty. This is actually more correct per the RAGFlow API contract ("Required unless document_ids is provided"), but the PR description states "keeps the existing APIs and configuration validation unchanged." Consider mentioning this in the PR description for reviewer awareness, and confirming that the RAGFlow server handles a missing dataset_ids field the same way it handled "dataset_ids": null.
There was a problem hiding this comment.
Thanks for pointing this out. I rechecked the valid configuration path: datasetIds defaults to an empty list, and RAGFlowConfig requires either non-empty dataset IDs or document IDs, so the previous document-only payload was "dataset_ids": [], rather than null.
The updated client omits dataset_ids when the effective list is empty. This matches the documented retrieval contract, which allows document_ids to be provided instead. Current RAGFlow main checks if not req.get("dataset_ids"), so an omitted field and an empty list currently take the same rejection path before document_ids is processed; this wire-format normalization does not remove a previously working current-main path, but it does expose an upstream documentation/implementation mismatch.
I updated the PR description to disclose the serialization change and added testRetrieveWithDocumentOnlyConfigOmitsDatasetIds in fe4b3140. The focused knowledge test passes 16/16, and the full RAGFlow test selection passes 98/98.
| Map<String, Object> effectiveMetadataCondition = | ||
| metadataCondition != null && !metadataCondition.isEmpty() | ||
| ? new HashMap<>(metadataCondition) | ||
| : copyMap(config.getMetadataCondition()); |
There was a problem hiding this comment.
[nit] The shallow copy via new HashMap<>(metadataCondition) protects against top-level mutations (put/remove), but nested mutable values inside the map are still shared between the caller and the deferred request. The Javadoc states "Parameter values are copied … so that later caller mutations cannot affect the asynchronous request," which is slightly overreaching. In practice this is unlikely to matter since metadata condition values are typically primitives/strings, but consider either softening the Javadoc claim (e.g. "top-level copies") or noting the limitation.
There was a problem hiding this comment.
Good catch. new HashMap<>(metadataCondition) snapshots only the top-level map structure; nested mutable lists or maps remain shared, and RAGFlow metadata conditions can contain nested condition objects.
I addressed the overbroad wording in fe4b3140: the Javadoc and PR description now state that the dataset ID list and top-level metadata-condition map are copied, while nested mutable metadata values are not deep-copied. I kept a generic deep-copy mechanism out of this focused PR because Map<String, Object> can carry arbitrary JSON-serializable values; a true deep-snapshot guarantee would need an explicit implementation and regression contract.
The focused knowledge test passes 16/16, and the full RAGFlow test selection passes 98/98.
| Map<String, Object> metadataCondition) { | ||
|
|
||
| List<String> effectiveDatasetIds = | ||
| datasetIds != null && !datasetIds.isEmpty() |
There was a problem hiding this comment.
[praise] Good defensive-copying pattern. Copying datasetIds and metadataCondition eagerly (before Mono.fromCallable) ensures that mutations on the caller's collections after this method returns cannot affect the in-flight async request. Clean implementation.
| void testRetrieveWithDynamicFiltersAndConfigFallback() throws Exception { | ||
| mockWebServer.enqueue(createSuccessResponse()); | ||
| mockWebServer.enqueue(createSuccessResponse()); | ||
| mockWebServer.enqueue(createSuccessResponse()); |
There was a problem hiding this comment.
[praise] Good test coverage: verifies two consecutive requests with different dynamic filters produce distinct request bodies, and that empty/null parameters correctly fall back to config values. This gives confidence that request isolation and config fallback both work as intended.
AgentScope-Java Version
2.0.3-SNAPSHOT
Description
Closes #686.
RAGFlow retrieval filters were bound to
RAGFlowConfigwhen aRAGFlowKnowledgeinstance was created, so application code could not changedatasetIdsormetadataConditionbetween retrieval calls without rebuilding the knowledge or agent setup.This PR:
datasetIdsandmetadataConditionoverloads toRAGFlowKnowledgeandRAGFlowClient;RAGFlowConfigfornullor empty values;Request semantics
For a document-only configuration, the previous client serialized
"dataset_ids": []. The updated client omitsdataset_idswhen the effective list is empty, matching the documented RAGFlow retrieval contract that allowsdocument_idsto be provided instead.Validation
Result: 98 tests passed, 0 failures, 0 errors, 0 skipped. Spotless also passed as part of the Maven build.
Checklist
mvn test)