Skip to content

feat(indexing): implement topic tagging pipeline - #528

Merged
hedhoud merged 3 commits into
refactor/hexagonalfrom
feat/topic-tagging-indexer
Jun 22, 2026
Merged

feat(indexing): implement topic tagging pipeline#528
hedhoud merged 3 commits into
refactor/hexagonalfrom
feat/topic-tagging-indexer

Conversation

@hedhoud

@hedhoud hedhoud commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Context

Topic tagging was exposed through indexation config, but it did not actually generate or store any tags. That made the preset flag misleading and left retrieval/facet work without usable data.

Change

This PR turns the scaffold into a working backend slice: topic tags are generated during indexing when enabled, normalized and deduplicated, then stored in Postgres with partition-scoped cleanup on re-index or file deletion.

Retrieval ranking and UI filtering are intentionally left for follow-up work; this PR makes the indexed data exist first.

Closes #523.

Validation

  • uv run --no-env-file pytest tests/unit
  • uv run --no-env-file ruff check openrag tests/unit/core/indexing/test_topic_tags.py tests/unit/services/persistence/test_topic_tag_repo.py tests/unit/services/workers/test_pipeline_builder.py tests/unit/services/workers/test_indexer_worker.py tests/unit/services/workers/test_indexer_pool.py
  • uv run --no-env-file ruff format --check openrag tests/unit/core/indexing/test_topic_tags.py tests/unit/services/persistence/test_topic_tag_repo.py tests/unit/services/workers/test_pipeline_builder.py tests/unit/services/workers/test_indexer_worker.py tests/unit/services/workers/test_indexer_pool.py
  • cd openrag/services/persistence/migrations/alembic && uv run --no-env-file alembic heads

Summary by CodeRabbit

  • New Features
    • Added automatic LLM-based document topic tagging during indexing, generating topic_tags per document/partition.
    • Introduced a new topic-tagging prompt and end-to-end support for persisting and retrieving topic tags, enabling topic-based search.
  • Improvements
    • Enhanced tag normalization/deduplication and robust handling of invalid/timeout responses.
    • Updated indexing and storage logic to reconcile topic tags with configuration.
  • Tests
    • Added unit tests for tag extraction, repository behavior, and pipeline/worker wiring.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a1e406b8-8243-4734-b1b8-0f2e4ae10147

📥 Commits

Reviewing files that changed from the base of the PR and between 7566e48 and 8920caf.

📒 Files selected for processing (1)
  • tests/unit/services/workers/test_indexer_pool.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/services/workers/test_indexer_pool.py

📝 Walkthrough

Walkthrough

Implements end-to-end document-level topic tagging for the indexing pipeline. Adds a TopicTagger class that calls an LLM to extract normalized topic tags from document chunks, a new topic_tag_stage pipeline stage, a topic_tags database table with Alembic migration, a full asyncpg PgTopicTagRepository replacing the prior stub, and worker wiring in IndexerPool/IndexerWorker to produce and persist tags per document.

Changes

Topic Tagging Feature

Layer / File(s) Summary
Core contracts and configuration
openrag/core/models/prompt.py, openrag/core/config/infrastructure.py, openrag/core/ports/topic_tag_repo.py, openrag/prompts/templates/topic_tagger_tmpl.txt
PromptType.TOPIC_TAGGER enum value added; PromptsConfig gains topic_tagger field mapping to the template; TopicTagRepository abstract methods updated to require partition parameter; prompt template constrains LLM output to JSON tag arrays with language and specificity guidance.
TopicTagger LLM class and unit tests
openrag/core/indexing/topic_tags.py, tests/unit/core/indexing/test_topic_tags.py
New TopicTagger class with tag() async method, message builder, JSON parser with bracket-fallback and dict-wrapper support, tag normalizer/deduplicator; handles multi-shape LLM responses (raw string, OpenAI choices, top-level content) and timeouts. Unit tests cover structured extraction, invalid responses, edge cases (max_tags ≤ 0, timeout), and JSON parsing with trailing text.
Database schema and migration
openrag/services/persistence/schema.py, openrag/services/persistence/migrations/alembic/versions/b7c8d9e0f1a2_add_topic_tags.py
New SQLAlchemy topic_tags table with composite FK to files (CASCADE delete), unique constraint on (document_id, partition, normalized_tag), and lookup indexes; Alembic migration with guarded conditional operations to handle existing deployments.
PgTopicTagRepository asyncpg implementation and unit tests
openrag/services/persistence/topic_tag_repo.py, tests/unit/services/persistence/test_topic_tag_repo.py
Replaces stub with full asyncpg-backed repository: bulk_insert (normalizes, deduplicates, upserts via INSERT ON CONFLICT), get_by_document and delete_by_document (partition-filtered with affected-count parsing), search (partition and tag scoped, case-insensitive). Includes tag normalization helpers and unit tests for all query paths.
topic_tag_stage and pipeline builder integration
openrag/services/workers/stages/topic_tag.py, openrag/services/workers/pipeline_builder.py, tests/unit/services/workers/test_pipeline_builder.py
New topic_tag_stage async worker stage with chunk validation, optional-timeout invocation, and success/failure row state. PipelineTimeouts and IndexingPipeline extended with topic-tag timeout and tagger/factory fields; _select_topic_tagger() gates tagging via config flag and resolves LLM factory. build_indexing_pipeline() factory accepts and wires topic-tagger dependencies. Tests verify enable/disable paths.
IndexerPool factory and IndexerWorker tag reconciliation
openrag/services/workers/indexer_pool.py, openrag/services/workers/indexer_actor.py, tests/unit/services/workers/test_indexer_pool.py, tests/unit/services/workers/test_indexer_worker.py
IndexerPool builds cached, lock-protected _build_topic_tagger_factory that resolves named/default LLM configs and passes factory and topic_tag_repo to pipeline and worker. IndexerWorker adds _replace_topic_tags_if_needed to delete then bulk-insert tags per (file_id, partition), gated by enable_topic_tagging. Tests validate factory LLM resolution and worker tag replacement lifecycle.

Sequence Diagram(s)

sequenceDiagram
  participant IndexerPool
  participant IndexingPipeline
  participant topic_tag_stage
  participant TopicTagger
  participant LLM
  participant IndexerWorker
  participant PgTopicTagRepository

  rect rgba(70, 130, 180, 0.5)
    note over IndexerPool: Build phase
    IndexerPool->>IndexerPool: _build_topic_tagger_factory(cfg)
    IndexerPool->>IndexingPipeline: build_indexing_pipeline(topic_tagger_factory=...)
    IndexerPool->>IndexerWorker: __init__(topic_tag_repo=pg_repo)
  end

  rect rgba(60, 179, 113, 0.5)
    note over IndexerWorker: process_file per document
    IndexerWorker->>IndexingPipeline: run(row, config)
    IndexingPipeline->>topic_tag_stage: row["chunks"]
    topic_tag_stage->>TopicTagger: tag(chunks, filename, max_tags)
    TopicTagger->>LLM: chat(system+user messages)
    LLM-->>TopicTagger: JSON array response
    TopicTagger-->>topic_tag_stage: list[str] normalized tags
    topic_tag_stage-->>IndexingPipeline: row["topic_tags"] populated
    IndexingPipeline-->>IndexerWorker: completed row
    IndexerWorker->>PgTopicTagRepository: delete_by_document(document_id, partition)
    IndexerWorker->>PgTopicTagRepository: bulk_insert(tag_records)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • linagora/openrag#333: Introduced the initial TopicTagRepository port interface; this PR updates its method signatures to include partition parameter.
  • linagora/openrag#419: Introduced stage helper functions (scrub_credentials, run_with_optional_timeout) that the new topic_tag_stage reuses.
  • linagora/openrag#444: Added the enable_topic_tagging, max_topic_tags, and topic_tagging_llm fields to IndexationPipelineConfig; this PR implements the end-to-end pipeline stage that consumes those config values.

Suggested reviewers

  • paultranvan

Poem

🐇 Hop hop, the tagger's alive at last!
No more empty tag tables in the past.
Chunks go in, JSON arrays come out,
Normalized, deduped — what's that all about?
The rabbit stamps topics with a careful paw,
And the pipeline finally fills without a flaw! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.64% 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 'feat(indexing): implement topic tagging pipeline' directly and concisely describes the main change: implementing a complete topic tagging feature in the indexing pipeline.
Linked Issues check ✅ Passed The PR fully implements the index-time generation objective from issue #523, adding LLM-based topic extraction, normalization, deduplication, persistence, and partition-scoped cleanup via pipeline stage, repository methods, and configuration integration.
Out of Scope Changes check ✅ Passed All changes are within scope: topic tagging infrastructure (LLM stage, repository, templates, migrations), indexing pipeline wiring, worker integration, and comprehensive unit tests. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/topic-tagging-indexer

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 and usage tips.

@coderabbitai coderabbitai Bot added the feat Add a new feature label Jun 19, 2026
@hedhoud
hedhoud requested a review from andyne13 June 19, 2026 13:56

@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: 4

🧹 Nitpick comments (7)
openrag/services/persistence/schema.py (1)

296-307: 💤 Low value

Consider adding topic_tags to __all__ for consistency.

Other tables like files, users, and partition_memberships are exported via __all__. Omitting topic_tags creates an inconsistency, though it doesn't break functionality since the table can still be imported directly.

🤖 Prompt for 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.

In `@openrag/services/persistence/schema.py` around lines 296 - 307, The
`topic_tags` table is missing from the `__all__` list in the schema.py file,
creating an inconsistency with other exported tables like `files`, `users`, and
`partition_memberships`. Add `topic_tags` to the `__all__` list to ensure it is
properly exported alongside the other table definitions, maintaining consistency
in the module's public API.
openrag/services/persistence/migrations/alembic/versions/b7c8d9e0f1a2_add_topic_tags.py (1)

22-63: 💤 Low value

Redundant unique constraint creation block.

The UniqueConstraint is already defined within create_table (lines 43-48), so the separate check and creation at lines 57-62 will always find the constraint already exists. This block is a no-op in normal operation and can be removed for clarity.

♻️ Proposed simplification
     if not index_exists("topic_tags", "ix_topic_tags_partition_tag"):
         op.create_index("ix_topic_tags_partition_tag", "topic_tags", ["partition", "normalized_tag"])
-    if not unique_constraint_exists("topic_tags", "uix_topic_tags_document_partition_tag"):
-        op.create_unique_constraint(
-            "uix_topic_tags_document_partition_tag",
-            "topic_tags",
-            ["document_id", "partition", "normalized_tag"],
-        )
🤖 Prompt for 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.

In
`@openrag/services/persistence/migrations/alembic/versions/b7c8d9e0f1a2_add_topic_tags.py`
around lines 22 - 63, The upgrade function contains a redundant unique
constraint creation block. The UniqueConstraint with name
uix_topic_tags_document_partition_tag is already defined within the
op.create_table call for the topic_tags table, so the separate check using
unique_constraint_exists and the subsequent op.create_unique_constraint call at
the end of the upgrade function is unnecessary and will always be a no-op.
Remove the entire block that checks if the unique constraint exists and attempts
to create it, as the constraint will be automatically created as part of the
table definition.
tests/unit/services/persistence/test_topic_tag_repo.py (1)

75-88: ⚡ Quick win

Consider adding test coverage for delete_by_document.

The test file covers bulk_insert, get_by_document, and search, but delete_by_document is untested. A simple test verifying the query structure and _delete_count parsing would complete the coverage.

💚 Proposed test
`@pytest.mark.asyncio`
async def test_delete_by_document_returns_affected_count():
    from services.persistence.topic_tag_repo import PgTopicTagRepository

    pool = _FakePool()
    repo = PgTopicTagRepository(pool_getter=lambda: pool)

    count = await repo.delete_by_document("file-1", partition="tenant-a")

    assert count == 2  # _FakePool.execute returns "DELETE 2"
    query, params = pool.executed[0]
    assert "DELETE FROM topic_tags" in query
    assert "WHERE document_id = $1 AND partition = $2" in query
    assert params == ("file-1", "tenant-a")
🤖 Prompt for 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.

In `@tests/unit/services/persistence/test_topic_tag_repo.py` around lines 75 - 88,
The test file is missing coverage for the delete_by_document method of
PgTopicTagRepository. Add a new test function named
test_delete_by_document_returns_affected_count that creates a _FakePool
instance, instantiates PgTopicTagRepository with the pool, calls the
delete_by_document method with parameters like "file-1" and
partition="tenant-a", and then verifies the executed query contains the DELETE
FROM statement, has the correct WHERE clause with document_id and partition
conditions, and that the parameters are passed in the correct order as the
method expects.
openrag/services/persistence/topic_tag_repo.py (1)

145-152: 💤 Low value

Duplicate normalization logic across layers.

_normalize_display_tag here duplicates similar logic in openrag/core/indexing/topic_tags.py. Both collapse whitespace and truncate to 80 characters. If the normalization rules evolve, both locations must be updated in sync.

Consider extracting a shared helper to openrag/core/utils/ if this becomes a maintenance concern, though the current duplication is acceptable given the layer separation.

🤖 Prompt for 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.

In `@openrag/services/persistence/topic_tag_repo.py` around lines 145 - 152, The
normalization logic in _normalize_display_tag (collapsing whitespace and
truncating to 80 characters) is duplicated in
openrag/core/indexing/topic_tags.py. If maintaining synchronized normalization
rules across both locations becomes problematic during future changes, extract a
shared helper function to openrag/core/utils/ that both _normalize_display_tag
in topic_tag_repo.py and the similar logic in topic_tags.py can import and use,
ensuring consistent behavior across layers.
tests/unit/core/indexing/test_topic_tags.py (1)

16-39: ⚡ Quick win

Add regression tests for timeout-zero and bracket-suffix parsing.

Please add focused cases for:

  1. timeout_seconds=0 still using timeout path, and
  2. parsing output like ["a"] [Sources: 1] to keep ["a"].
🤖 Prompt for 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.

In `@tests/unit/core/indexing/test_topic_tags.py` around lines 16 - 39, Add two
new test functions to the file following the same pattern as the existing tests.
First, create a test for the timeout_seconds=0 edge case that verifies the
TopicTagger still applies timeout handling even when timeout_seconds is set to
zero. Second, create a test for parsing responses with bracket suffixes where
the TopicTagger should correctly extract only the JSON array portion and ignore
trailing content like "[Sources: 1]", using FakeLLM to simulate an LLM response
in the format ["a"] [Sources: 1] and verify the tagger returns only ["a"].
openrag/core/ports/topic_tag_repo.py (1)

15-18: ⚡ Quick win

Make partition required on document-scoped read/delete methods.

Line 15 and Line 18 currently allow partition=None, which makes accidental cross-partition reads/deletes easier in future callers. Given the rest of this feature is partition-scoped, requiring partition in the port is safer.

Suggested contract tightening
-    async def get_by_document(self, document_id: str, partition: str | None = None) -> list[dict]: ...
+    async def get_by_document(self, document_id: str, partition: str) -> list[dict]: ...

-    async def delete_by_document(self, document_id: str, partition: str | None = None) -> int: ...
+    async def delete_by_document(self, document_id: str, partition: str) -> int: ...
🤖 Prompt for 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.

In `@openrag/core/ports/topic_tag_repo.py` around lines 15 - 18, The
`get_by_document` and `delete_by_document` methods in TopicTagRepo currently
have optional partition parameters with `partition: str | None = None`, which
increases the risk of accidental cross-partition operations. Remove the `| None`
union type and the `= None` default value from the partition parameter in both
methods to make partition a required argument, ensuring all callers must
explicitly specify which partition to operate on.
tests/unit/services/workers/test_indexer_worker.py (1)

408-439: ⚡ Quick win

Add a branch test for “disabled topic tagging triggers delete-only cleanup.”

Please add a focused case where indexation_config={"enable_topic_tagging": False} and no row["topic_tags"] is produced, then assert delete_by_document is called and bulk_insert is not. This protects the stale-tag cleanup contract.

🤖 Prompt for 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.

In `@tests/unit/services/workers/test_indexer_worker.py` around lines 408 - 439,
Add a new test function after
test_process_file_replaces_topic_tags_after_successful_pipeline that validates
the delete-only cleanup behavior when topic tagging is disabled. Create a test
case where IndexerWorker is initialized with indexation_config set to disable
topic tagging (enable_topic_tagging False), use a TaggingPipeline that does not
produce topic_tags in the row output, call worker.process_file with the same
parameters, and then assert that the FakeTopicTagRepo's deleted list contains
the document cleanup entry while the inserted list remains empty, verifying that
stale tags are removed when topic tagging is disabled.
🤖 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/core/indexing/topic_tags.py`:
- Around line 33-45: The tag method needs an early guard clause to handle
non-positive max_tags values. Currently, when max_tags is 0 or negative, the
function can still return one tag because validation happens after appending at
line 104. Add a check right after the chunks list conversion and empty chunks
check in the tag method that returns an empty list if max_tags is less than or
equal to 0, ensuring the function returns deterministically for invalid max_tags
values before any tagging logic executes.
- Around line 113-118: The regex pattern in the tag extraction logic uses a
greedy quantifier that matches from the first opening bracket to the last
closing bracket in the text, causing multi-bracket responses like ["finance"]
[Sources: 1] to be treated as a single invalid JSON string. Fix this by changing
the greedy quantifier in the regex pattern from `*` to `*?` in the
`re.search(r"\[[\s\S]*\]", text)` call to make it non-greedy, so it will match
only the shortest bracket-enclosed segment and correctly extract valid JSON tags
instead of discarding them.
- Line 55: The timeout check on line 55 uses a truthy evaluation (`if
self._timeout`) which treats 0 as falsy and skips the asyncio.wait_for timeout
enforcement entirely. Replace the truthy check with an explicit None check by
changing `if self._timeout` to `if self._timeout is not None` in the conditional
expression. This ensures that a timeout_seconds value of 0 is properly
recognized as a valid timeout configuration and the timeout enforcement is
applied correctly.

In `@openrag/services/workers/indexer_actor.py`:
- Around line 152-154: The code at line 153 iterates over row.get("topic_tags",
[]) without validating its type first, and the delete_by_document call happens
at line 152 before any validation occurs. This means if row["topic_tags"] is a
string instead of a list, it will iterate character-by-character creating
invalid tags, and if an error occurs during iteration, the existing tags are
already deleted with no rollback. Add type validation for row["topic_tags"]
before the delete_by_document call on line 152 to ensure it is a list, either by
checking isinstance(row.get("topic_tags"), list) or coercing the value to a list
type, so that validation happens before any destructive operations.

---

Nitpick comments:
In `@openrag/core/ports/topic_tag_repo.py`:
- Around line 15-18: The `get_by_document` and `delete_by_document` methods in
TopicTagRepo currently have optional partition parameters with `partition: str |
None = None`, which increases the risk of accidental cross-partition operations.
Remove the `| None` union type and the `= None` default value from the partition
parameter in both methods to make partition a required argument, ensuring all
callers must explicitly specify which partition to operate on.

In
`@openrag/services/persistence/migrations/alembic/versions/b7c8d9e0f1a2_add_topic_tags.py`:
- Around line 22-63: The upgrade function contains a redundant unique constraint
creation block. The UniqueConstraint with name
uix_topic_tags_document_partition_tag is already defined within the
op.create_table call for the topic_tags table, so the separate check using
unique_constraint_exists and the subsequent op.create_unique_constraint call at
the end of the upgrade function is unnecessary and will always be a no-op.
Remove the entire block that checks if the unique constraint exists and attempts
to create it, as the constraint will be automatically created as part of the
table definition.

In `@openrag/services/persistence/schema.py`:
- Around line 296-307: The `topic_tags` table is missing from the `__all__` list
in the schema.py file, creating an inconsistency with other exported tables like
`files`, `users`, and `partition_memberships`. Add `topic_tags` to the `__all__`
list to ensure it is properly exported alongside the other table definitions,
maintaining consistency in the module's public API.

In `@openrag/services/persistence/topic_tag_repo.py`:
- Around line 145-152: The normalization logic in _normalize_display_tag
(collapsing whitespace and truncating to 80 characters) is duplicated in
openrag/core/indexing/topic_tags.py. If maintaining synchronized normalization
rules across both locations becomes problematic during future changes, extract a
shared helper function to openrag/core/utils/ that both _normalize_display_tag
in topic_tag_repo.py and the similar logic in topic_tags.py can import and use,
ensuring consistent behavior across layers.

In `@tests/unit/core/indexing/test_topic_tags.py`:
- Around line 16-39: Add two new test functions to the file following the same
pattern as the existing tests. First, create a test for the timeout_seconds=0
edge case that verifies the TopicTagger still applies timeout handling even when
timeout_seconds is set to zero. Second, create a test for parsing responses with
bracket suffixes where the TopicTagger should correctly extract only the JSON
array portion and ignore trailing content like "[Sources: 1]", using FakeLLM to
simulate an LLM response in the format ["a"] [Sources: 1] and verify the tagger
returns only ["a"].

In `@tests/unit/services/persistence/test_topic_tag_repo.py`:
- Around line 75-88: The test file is missing coverage for the
delete_by_document method of PgTopicTagRepository. Add a new test function named
test_delete_by_document_returns_affected_count that creates a _FakePool
instance, instantiates PgTopicTagRepository with the pool, calls the
delete_by_document method with parameters like "file-1" and
partition="tenant-a", and then verifies the executed query contains the DELETE
FROM statement, has the correct WHERE clause with document_id and partition
conditions, and that the parameters are passed in the correct order as the
method expects.

In `@tests/unit/services/workers/test_indexer_worker.py`:
- Around line 408-439: Add a new test function after
test_process_file_replaces_topic_tags_after_successful_pipeline that validates
the delete-only cleanup behavior when topic tagging is disabled. Create a test
case where IndexerWorker is initialized with indexation_config set to disable
topic tagging (enable_topic_tagging False), use a TaggingPipeline that does not
produce topic_tags in the row output, call worker.process_file with the same
parameters, and then assert that the FakeTopicTagRepo's deleted list contains
the document cleanup entry while the inserted list remains empty, verifying that
stale tags are removed when topic tagging is disabled.
🪄 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

Run ID: 0b31e42f-fafd-43ef-b9ff-b6ecb6313495

📥 Commits

Reviewing files that changed from the base of the PR and between 04d0266 and cf08644.

📒 Files selected for processing (17)
  • openrag/core/config/infrastructure.py
  • openrag/core/indexing/topic_tags.py
  • openrag/core/models/prompt.py
  • openrag/core/ports/topic_tag_repo.py
  • openrag/prompts/templates/topic_tagger_tmpl.txt
  • openrag/services/persistence/migrations/alembic/versions/b7c8d9e0f1a2_add_topic_tags.py
  • openrag/services/persistence/schema.py
  • openrag/services/persistence/topic_tag_repo.py
  • openrag/services/workers/indexer_actor.py
  • openrag/services/workers/indexer_pool.py
  • openrag/services/workers/pipeline_builder.py
  • openrag/services/workers/stages/topic_tag.py
  • tests/unit/core/indexing/test_topic_tags.py
  • tests/unit/services/persistence/test_topic_tag_repo.py
  • tests/unit/services/workers/test_indexer_pool.py
  • tests/unit/services/workers/test_indexer_worker.py
  • tests/unit/services/workers/test_pipeline_builder.py

Comment thread openrag/core/indexing/topic_tags.py
Comment thread openrag/core/indexing/topic_tags.py Outdated
Comment thread openrag/core/indexing/topic_tags.py Outdated
Comment thread openrag/services/workers/indexer_actor.py

@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.

🧹 Nitpick comments (1)
tests/unit/services/workers/test_indexer_pool.py (1)

286-290: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Assert ray.get_actor namespace in this wiring test.

The stub currently ignores call arguments, so a regression dropping namespace="openrag" would still pass.

Suggested test hardening
-    monkeypatch.setattr(module.ray, "get_actor", lambda *args, **kwargs: object())
+    actor_calls: list[tuple[tuple[Any, ...], dict[str, Any]]] = []
+    def _fake_get_actor(*args: Any, **kwargs: Any) -> object:
+        actor_calls.append((args, kwargs))
+        return object()
+    monkeypatch.setattr(module.ray, "get_actor", _fake_get_actor)
     monkeypatch.setattr(module, "IndexerWorker", Worker)

     actor_class = module.IndexerPool.__ray_metadata__.modified_class
     actor_class()

+    assert actor_calls
+    assert actor_calls[0][0][0] == "TaskStateManager"
+    assert actor_calls[0][1].get("namespace") == "openrag"
     assert captured["contextualizer_factory"] is contextualizer_factory
     assert captured["topic_tagger_factory"] is topic_tagger_factory

As per coding guidelines, Ray actors should be accessed via ray.get_actor() using namespace "openrag".

🤖 Prompt for 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.

In `@tests/unit/services/workers/test_indexer_pool.py` around lines 286 - 290, The
monkeypatch for module.ray.get_actor currently uses a lambda that ignores all
arguments, making it impossible to verify that the correct namespace parameter
is being passed. Replace the lambda mock with a proper mock object (using
unittest.mock.MagicMock or similar) that can capture call arguments, then add an
assertion after the actor_class() call to verify that ray.get_actor was called
with the namespace parameter set to "openrag". This ensures that any future
regression where the namespace argument is accidentally removed from the
ray.get_actor call will be caught by the test.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@tests/unit/services/workers/test_indexer_pool.py`:
- Around line 286-290: The monkeypatch for module.ray.get_actor currently uses a
lambda that ignores all arguments, making it impossible to verify that the
correct namespace parameter is being passed. Replace the lambda mock with a
proper mock object (using unittest.mock.MagicMock or similar) that can capture
call arguments, then add an assertion after the actor_class() call to verify
that ray.get_actor was called with the namespace parameter set to "openrag".
This ensures that any future regression where the namespace argument is
accidentally removed from the ray.get_actor call will be caught by the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 04bea148-b114-43a3-8839-81f835b2b6d7

📥 Commits

Reviewing files that changed from the base of the PR and between cf08644 and 7566e48.

📒 Files selected for processing (11)
  • openrag/core/indexing/topic_tags.py
  • openrag/core/ports/topic_tag_repo.py
  • openrag/services/persistence/migrations/alembic/versions/b7c8d9e0f1a2_add_topic_tags.py
  • openrag/services/persistence/schema.py
  • openrag/services/persistence/topic_tag_repo.py
  • openrag/services/workers/indexer_actor.py
  • openrag/services/workers/indexer_pool.py
  • tests/unit/core/indexing/test_topic_tags.py
  • tests/unit/services/persistence/test_topic_tag_repo.py
  • tests/unit/services/workers/test_indexer_pool.py
  • tests/unit/services/workers/test_indexer_worker.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/unit/services/persistence/test_topic_tag_repo.py
  • openrag/services/persistence/migrations/alembic/versions/b7c8d9e0f1a2_add_topic_tags.py
  • openrag/core/indexing/topic_tags.py
  • openrag/services/persistence/topic_tag_repo.py
  • openrag/services/workers/indexer_pool.py

@hedhoud
hedhoud merged commit 4037ba9 into refactor/hexagonal Jun 22, 2026
6 checks passed
@hedhoud
hedhoud deleted the feat/topic-tagging-indexer branch June 22, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat Add a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant