Problem/Motivation

When chunking content for AI Search (chunking being fundamental for vector databases), sometimes an item is so long that it cannot be indexed in a single go and needs have multiple runs in the batch to complete it. This is typically worked around by e.g. using LLM to shorter content (high token usage and cost) or having much larger chunk size (less representative vectors, so less accuracy).

We have attempted with subbatch here #3487487: Improve AI Search Module Indexing to Handle Long-Running Chunk Embedding Processes at first but the UX for subbatch is very confusing, the end user sees progress bar after progress bar and has no real concept of when it'll finish in a big site.

If we could override the ::create() method of the service, we could extend it to have our own implementation that modifies the run so e.g. when indexing 10 items, if item number 4 needs 2 runs, we can then have it hit that again.

Steps to reproduce

Described above

Proposed resolution

Change IndexBatchHelper to a service so it can be overridden by AI Search module (submodule of AI module)

Remaining tasks

MR

Issue fork search_api-3551144

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

scott_euser created an issue. See original summary.

scott_euser’s picture

Assigned: scott_euser » Unassigned
Status: Active » Needs review
Related issues: +#3487487: Improve AI Search Module Indexing to Handle Long-Running Chunk Embedding Processes

This looks much bigger than it is because of the interface; but really could be without interface and leave IndexBatchHelper completely untouched if you prefer.

Considered modifying CommandHelper constructor but that is also used by search_api_solr for example and given the IndexBatchHelper is already just calling static method ::create() from there, figured \Drupal::service() would be okay there, but feel free to disagree if you think coordinated release is better like we did with the Search API Fields indexing page.

Thanks!

drunken monkey made their first commit to this issue’s fork.

drunken monkey’s picture

Status: Needs review » Needs work

Thanks for posting this suggestion!
I don’t quite understand the whole reasoning, but in principle this should always have been a service, just like the other helper services in the Utility namespace. So, yes, let’s do it.

However, just making this a service without any changes doesn’t make much sense to me. Definitely, all those methods should be instance methods, not static.
I rewrote the MR in that direction and added a change record. Please tell me what you think.

Still needs a bit of work to get rid of static \Drupal::…() calls in the service class, but ran out of time today. Feel free to tackle it if you’re able, otherwise I’ll get to it tomorrow.

drunken monkey’s picture

Assigned: Unassigned » drunken monkey
drunken monkey’s picture

Assigned: drunken monkey » Unassigned
Status: Needs work » Needs review

OK, DI is complete and pipelines should be passing. Handing off for review.

One thing, now that we’re creating a new class anyways: Would you say IndexingBatchHelper is a better name than IndexBatchHelper? The latter sounds a bit awkward to me, or maybe more like it handles some operation on an index entity, not an indexing operation.
Also, having a slightly different name might help in distinguishing the old and the new class. (Other naming suggestions also welcome.)

drunken monkey’s picture

Issue tags: +Vienna2025
scott_euser’s picture

Assigned: Unassigned » scott_euser

Thanks! Will take a look asap, much appreciated!

Ultimately the reasoning for the change is because e.g. a single entity can take multiple passes from the batch to complete when generating embeddings as it can be very long running for huge processes.

So indexing 5 items might take 8 passes in the batch if 1 item needs 3 extra passes before LLM has generated all embeddings.

First step which we do programmatically is Chunk the content using an embedding strategy (usually a combination of breaking into chunks + re-adding things like title, short description as contextual content so the chunk doesn't loose its overall semantic meaning once converted to an embedding (set of vectors). This is quick so we quickly know that e.g. a giant article had 45 chunks, and we can estimate that roughly 10 chunks can be processed per batch.

So it means we need to revisit the progress and slow it down for that long running item, potentially something like this:

  • item 1 complete, less than 10 chunks: 20%
  • item 2 start, 45 chunks, first 10 done: 22.5%
  • item 2 continued, 45 chunks, 20 done: 25%
  • item 2 continued, 45 chunks, 30 done: 27.5%
  • etc...

Whereas currently the index helper progress start/finish is determined and fixed from the start of the batch setup. So its that method that I'd want to extend to manipulate the progress specifically for AI Search.

We had originally tried sub-batch but the UI is very confusing as progress bar then keeps jumping from 0 to 100% and its really hard as a user to understand how close to the overall total we actually are.

scott_euser’s picture

Assigned: scott_euser » Unassigned
Status: Needs review » Reviewed & tested by the community

Thank you! This is perfect, I am able to then override it fine and AI Search to handle the chunking now.

I did the rename to IndexingBatchHelper - makes sense to me.

drunken monkey’s picture

Status: Reviewed & tested by the community » Fixed

Excellent, thanks a lot!
Merged.
Thanks again!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.