Skip to content

allowing scope retrieval via a list of file_ids - #761

Merged
Ahmath-Gadji merged 17 commits into
linagora:developfrom
ewan102:feat/chat-attachments-filter
Aug 25, 2026
Merged

allowing scope retrieval via a list of file_ids#761
Ahmath-Gadji merged 17 commits into
linagora:developfrom
ewan102:feat/chat-attachments-filter

Conversation

@ewan102

@ewan102 ewan102 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

lists sent by a POST /v1/chat/completions, used a lot of the workspace filter mechanism, adding a lists of the file_id effectively indexed on the extra.attachments, in order to know which one of the file_ids' lists were not indexed in RAG

Summary by CodeRabbit

  • New Features
    • Added support for Cozy-style chat attachments: attachments are resolved to validated, indexed file IDs and applied to retrieval.
    • Chat responses and streamed SSE metadata now include attachment usage when applicable.
    • Stream payloads can now include additional client-facing extra fields alongside source/truncation info.
  • Bug Fixes
    • Malformed, empty, or unindexed attachments safely fall back to unscoped retrieval; workspace scoping takes precedence.
    • partitions=["all"] uses union-style resolution, while mixing "all" with explicit partitions raises an error.
  • Tests
    • Expanded unit and integration coverage for attachment parsing, scoping, precedence, de-duplication, and empty-input behavior.

…at/completions, used a lot of the workspace filter mechanism, adding a lists of the file_id effectively indexed on the extra.attachments, in order to know which one of the file_ids' lists were not indexed in RAG
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

QueryService now resolves indexed attachment IDs within requested partition scopes, applies them to retrieval, and exposes the resolved IDs in synchronous and streaming response metadata. Workspace repositories support cross-partition existence checks, and source filtering accepts additional payload fields.

Changes

Cozy attachment support

Layer / File(s) Summary
Attachment parsing and retrieval scoping
openrag/core/ports/workspace_repo.py, openrag/services/orchestrators/query_service.py, openrag/services/orchestrators/workspace_service.py, openrag/services/persistence/workspace_repo.py
Attachment IDs are parsed, deduplicated, resolved against indexed files for requested partitions, and applied to retrieval filters, including "all" partition handling and workspace-scope precedence.
Attachment metadata in chat responses
openrag/services/orchestrators/query_service.py, openrag/core/utils/source_filtering.py
Synchronous and streaming responses include resolved attachment IDs through extended payload handling while preserving existing source-filtering behavior.
Attachment behavior coverage
tests/unit/services/orchestrators/test_query_service.py, tests/integration/repos/test_workspace_repo.py
Tests cover valid, malformed, empty, unindexed, duplicate, partition-scoped, cross-partition, and repository existence lookup behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant QueryService
  participant WorkspaceService
  participant WorkspaceRepository
  participant Retriever
  Client->>QueryService: Send chat request with attachments
  QueryService->>WorkspaceService: Resolve indexed attachment IDs
  WorkspaceService->>WorkspaceRepository: Query existing file IDs
  WorkspaceRepository-->>WorkspaceService: Return indexed IDs
  WorkspaceService-->>QueryService: Return resolved IDs
  QueryService->>Retriever: Retrieve with file_id filter
  Retriever-->>QueryService: Return chunks
  QueryService-->>Client: Return response or stream with attachments
Loading

Possibly related PRs

  • linagora/openrag#744: Both changes propagate file-level retrieval scoping from QueryService into retrieval filters.

Suggested reviewers: ahmath-gadji

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: enabling scope retrieval via a list of file_ids.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openrag/services/orchestrators/query_service.py`:
- Around line 704-707: The attachment extraction logic in query_service.py
(around metadata attachments) must treat malformed payloads as unscoped
retrieval: require attachments to be a list and include only entries whose id is
a non-empty string before passing IDs to _existing_file_ids(). Add unit coverage
in tests/unit/services/orchestrators/test_query_service.py at lines 546-562 for
scalar attachment payloads and non-string IDs, asserting retrieval proceeds
without an exception and remains unscoped.
- Around line 507-509: Update the attachment-ID resolution logic around the
concrete partition calculation in
openrag/services/orchestrators/query_service.py:507-509 to expand "all" into the
concrete partitions used by retrieval, look up each partition’s existing IDs,
and union them before building the filter and response metadata; do not return
every requested ID directly. Add an "all" test case in
tests/unit/services/orchestrators/test_query_service.py:619-660 asserting
nonexistent IDs are excluded from both the retrieval filter and
extra.attachments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c89373f-05f9-4c3f-ac35-e13a7daa323e

📥 Commits

Reviewing files that changed from the base of the PR and between 3016598 and 957e954.

📒 Files selected for processing (3)
  • openrag/core/utils/source_filtering.py
  • openrag/services/orchestrators/query_service.py
  • tests/unit/services/orchestrators/test_query_service.py

Comment thread openrag/services/orchestrators/query_service.py Outdated
Comment thread openrag/services/orchestrators/query_service.py Outdated
Comment thread openrag/services/orchestrators/query_service.py Outdated
Comment thread openrag/services/orchestrators/query_service.py Outdated
Comment thread openrag/services/orchestrators/query_service.py Outdated
Comment thread openrag/services/orchestrators/query_service.py Outdated
Comment thread openrag/services/orchestrators/query_service.py Outdated
@coderabbitai coderabbitai Bot mentioned this pull request Jul 24, 2026
Comment thread openrag/services/orchestrators/query_service.py
Comment thread openrag/services/orchestrators/query_service.py Outdated
Comment thread openrag/services/orchestrators/query_service.py Outdated
@Ahmath-Gadji Ahmath-Gadji self-assigned this Jul 27, 2026
Comment thread openrag/services/persistence/workspace_repo.py
PgWorkspaceRepository implemented it and WorkspaceService (typed
against WorkspaceRepository) already called it, but it was missing
from the abstract interface.
@Ahmath-Gadji

Copy link
Copy Markdown
Collaborator

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

ewan102 and others added 8 commits July 29, 2026 12:07
…at/completions, used a lot of the workspace filter mechanism, adding a lists of the file_id effectively indexed on the extra.attachments, in order to know which one of the file_ids' lists were not indexed in RAG
PgWorkspaceRepository implemented it and WorkspaceService (typed
against WorkspaceRepository) already called it, but it was missing
from the abstract interface.
The comment justified skipping an ownership check on the attachment ids by
saying file_id is ANDed with the server-fixed partition. That stopped being
the whole story when the "all" wildcard switched to an unscoped catalog
lookup: on that path there is no partition to AND against, and the safety
argument is the SUPER_ADMIN_MODE invariant instead. Spell both out, since
the comment exists to justify the absence of a check.
Wording requested in review. It is also the accurate word: the list reports
what retrieval was scoped to, not what the answer drew on — that is
extra.sources.
…tgres

get_existing_file_ids_any_partition backs attachment scoping for the
openrag-all wildcard, but every test reaching it went through a fake, so
its SQL was never executed anywhere in CI. Same for the partition-scoped
sibling. Both are now exercised by the repos suite, which runs against a
real database on every PR.

The two scoping cases are mutation-checked: dropping the partition clause
from the scoped query, or adding one to the unscoped query, each fails a
test rather than passing quietly.
The list is client-supplied, so repeats would otherwise be echoed into the
retrieval filter and back out through extra.attachments. The dedupe is in
the code; the docstring still described a plain subset.
@Ahmath-Gadji
Ahmath-Gadji force-pushed the feat/chat-attachments-filter branch from f104591 to 87bb7b0 Compare July 29, 2026 10:07
EnjoyBacon7
EnjoyBacon7 previously approved these changes Jul 29, 2026

@EnjoyBacon7 EnjoyBacon7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Scoping retrieval by a client-supplied attachment list, with the indexed subset reported back in extra.attachments.

Review trail — all raised issues resolved:

  • openrag-all no longer blocks attachments for SUPER_ADMIN_MODE admins. The wildcard takes a single unscoped catalog lookup rather than fail-closing to zero results, guarded against a mixed ["all", ...] list.
  • Per-partition lookups run concurrently instead of serially.
  • Client-supplied duplicate ids are deduplicated before they reach the filter and the response.
  • get_existing_file_ids_any_partition is declared on the WorkspaceRepository port, not just the Postgres implementation.

Verified beyond the diff

  • The filterless fallback in RetrieverPipeline drops only the temporal filter and preserves filter_params, so an attachment-scoped query that matches nothing can never silently widen to the whole partition.
  • ["all"] reaches this layer solely for a SUPER_ADMIN_MODE admin — get_partition_name expands it to memberships for everyone else, and "all" is a reserved partition name, so it cannot arrive via a membership list.
  • Both scoping queries are mutation-checked: changing either partition clause fails a test.

Left as follow-ups, not blockers

  • A present-but-entirely-malformed attachments payload (e.g. [{"file_id": ...}] instead of [{"id": ...}]) degrades to an unscoped partition search rather than an error. Deliberate and tested, but the absent case and the all-invalid case are distinguishable if you later want them to behave differently.
  • The attachment id list is unbounded and client-controlled; a cap would need a product call on truncate-vs-400.

Disclosure: four doc/test commits here are mine. The substantive code is @ewan102's, and I reviewed it before contributing.

@Ahmath-Gadji

Copy link
Copy Markdown
Collaborator

Suggested Improvements

I've reviewed the PR and identified two improvements worth considering:

1️⃣ Defensive Guard for Empty Partitions

Add an early return when the partitions list is empty to avoid unnecessary database queries:

async def _existing_file_ids(self, file_ids: Sequence[str], partitions: Sequence[str]) -> list[str]:
    """Order-preserving, deduplicated subset of ``file_ids`` indexed in ``partitions``.

    ``file_ids`` is client-supplied, so repeats are dropped rather than
    echoed back into the filter and into ``extra.attachments``.

    ``"all"`` (``SUPER_ADMIN_MODE`` wildcard) takes an unscoped lookup instead
    of a per-partition one.
    """
    if not partitions:
        return []
    if "all" in partitions:
        if len(partitions) > 1:
            raise ValueError("`partitions` cannot mix the wildcard with explicit values.")
        found = set(await self._workspace.get_existing_file_ids_any_partition(file_ids))
    else:
        results = await asyncio.gather(*(self._workspace.get_existing_file_ids(p, file_ids) for p in partitions))
        found = {fid for r in results for fid in r}
    return [fid for fid in dict.fromkeys(file_ids) if fid in found]

Why: When no partitions are accessible, the current code still tries to look up files. This guard short-circuits the method cleanly.


2️⃣ API Documentation for Attachments

Add a row to the metadata table in docs/content/docs/documentation/API.mdx documenting the new attachments field:

Parameter Type Description
attachments List[{"id": string}] Scope RAG retrieval to a list of indexed file IDs. Unindexed files are silently excluded. Useful for multi-file search within a partition. Only files accessible in the target partition(s) are retrieved.

Example request:

curl -X POST http://localhost:8080/v1/chat/completions \\
  -H "Authorization: Bearer $AUTH_TOKEN" \\
  -H "Content-Type: application/json" \\
  -d '{
    "model": "openrag-partition-name",
    "messages": [{"role": "user", "content": "summarize these files"}],
    "metadata": {
      "attachments": [
        {"id": "file-001"},
        {"id": "file-002"},
        {"id": "file-003"}
      ]
    }
  }'

Response: The extra.indexed_attachment_ids field shows which attachment IDs were actually indexed (deduplicated, order-preserved). Unknown or unindexed IDs are silently filtered out.


Both changes are optional polish — the PR implementation is solid as-is. ✅

@Ahmath-Gadji
Ahmath-Gadji merged commit 0ce6e7e into linagora:develop Aug 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants