Repository navigation
Fixes #9280. Add filter.* and modality=media to langchain4j-ingest - #9340
jamesnetherton merged 1 commit into
Conversation
4753a66 to
5c6f2c1
Compare
5c6f2c1 to
28197ef
Compare
jamesnetherton
left a comment
There was a problem hiding this comment.
Reviewed locally. The fixes from the #9290 review all carried over. A few new comments inline; the first two matter most.
On the two points left open in #9290: have the Camel issues been opened (answering filtered for a media file whose type can't be determined, and exposing parsePatterns/idAccepted)? A link here would let us follow them.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of James Netherton
| // logged at DEBUG | ||
| route.to(kameletUri(name, SINK_KAMELET, "sink", sink)).process(exchange -> { | ||
| IngestResult result = exchange.getMessage().getBody(IngestResult.class); | ||
| if (result != null && result.outcome() == IngestResult.Outcome.FILTERED) { |
There was a problem hiding this comment.
On a directory pipeline a filtered file's exchange succeeds, so its key is committed in the source register. With a persistent source.idempotent-repository, a file filtered by filter.exclude-id stays skipped after the filter is removed and the app restarted, unless the file is touched. filter.* is runtime config, so changing it is expected.
That is the same problem given in #9290 as the reason not to commit an untypable media file ("a committed key would keep the file skipped even after a config fix"). Should a filtered file roll back its key instead, or should usage.adoc at least warn about it alongside the persistent register docs?
There was a problem hiding this comment.
True, reproduced with a FileIdempotentRepository: the excluded file's key was persisted, and after removing the filter and restarting, the file was never ingested.
Fixed in 92c3c12. Releasing the key would re-read the file on every poll, so a named register on a directory pipeline is now wrapped (SourceRegister), which holds a filtered file's key in memory only. The file is skipped while the app runs and checked again after a restart. Test: Langchain4jIngestFilterTest.filteredFileStaysOutOfTheNamedRegister. usage.adoc updated.
This also removes my #9290 objection to answering filtered for an untypable media file.
Generated with Claude Code on behalf of @JiriOndrusek
| if (parser == null) { | ||
| // text is read as UTF-8; a parser receives the raw bytes instead - the format is | ||
| // its business, and a charset conversion would corrupt a binary document | ||
| if (parser == null && !media) { |
There was a problem hiding this comment.
With !media added here, a media pipeline now leaves charset out, so an application-wide camel.kamelet.langchain4j-ingest-file-source.charset fills it in. That goes against the "options at their defaults are passed all the same" rule at line 253. If I read GenericFileConverter right, a charset on the exchange makes the byte conversion go through a reader, which would corrupt WAV/PNG bytes with no error. The same applies to parser pipelines, though that was there before. Can it be pinned to a neutral value, or covered by the IT poison set like documentFilter?
There was a problem hiding this comment.
Right, reproduced both ways: with UTF-8, WAV and PDF files fail on every poll. With ISO-8859-1 and max-document-size set, the WAV is stored under a wrong vector and nothing is logged.
It can't be pinned: the file endpoint has no neutral charset ("" fails its validation). Fixed in 92c3c12: parser and media directory pipelines drop the charset right after the source, before anything reads the file. Text pipelines keep UTF-8 pinned.
The IT poison set now has camel.kamelet.langchain4j-ingest-file-source.charset=UTF-16. The audio, Tika and text tests pass with it.
Generated with Claude Code on behalf of @JiriOndrusek
| if (filter.minDocumentSize() != 0) { | ||
| sink.put("minDocumentSize", String.valueOf(filter.minDocumentSize())); | ||
| } | ||
| sink.put("documentFilter", "#bean:" + filter.documentFilter() |
There was a problem hiding this comment.
A misspelled filter.document-filter bean fails inside Kamelet property binding, with an error that names the sink route rather than the pipeline or the config key. repositoryRef() already checks the bean up front with a clear "references ... but no such bean exists" message. Could this get the same check?
There was a problem hiding this comment.
Done in 92c3c12, with the same message: Ingestion pipeline 'docs' references document filter 'no-such-filter' but no such bean exists. Test: IngestMissingDocumentFilterTest.
Generated with Claude Code on behalf of @JiriOndrusek
| } | ||
|
|
||
| // the component and IngestRoutes check these at startup; a configured pipeline fails the build instead | ||
| boolean media = "media".equalsIgnoreCase(pipeline.modality()); |
There was a problem hiding this comment.
Minor: the max-segment-size/max-overlap-size/embedding-batch-size checks above still run for modality=media, although the new Javadoc says they don't apply there. A media pipeline with settings copied from a text one (e.g. overlap 600, default segment 500) fails the build over options that are ignored. Skip them when media is true?
There was a problem hiding this comment.
Yes, done in 92c3c12. They are skipped for media, as the component does. Test: IngestMediaSplitterSettingsTest (overlap 600 and batch size 0 on a media pipeline, which builds and starts).
Generated with Claude Code on behalf of @JiriOndrusek
| String excludeId = filter.excludeId().orElse(""); | ||
| sink.put("includeId", includeId); | ||
| sink.put("excludeId", excludeId); | ||
| if (filter.minDocumentSize() != 0) { |
There was a problem hiding this comment.
Nit: minDocumentSize is put as "0" at line 270 and then conditionally overwritten here. A single sink.put("minDocumentSize", String.valueOf(filter.minDocumentSize())) would do.
There was a problem hiding this comment.
Done in 92c3c12.
Generated with Claude Code on behalf of @JiriOndrusek
filter.include-id, filter.exclude-id, filter.min-document-size and filter.document-filter go to the sink Kamelet and are enforced by the component (CAMEL-24753), for configured and @ingest pipelines alike. With a parser, the id patterns are also checked before the parse, so an excluded document is never parsed. A missing document-filter bean fails the start naming it. On a directory pipeline, a filtered file's key is held in memory rather than in a named, possibly persistent register, so a changed filter applies to the file after a restart. modality=media, with an optional content-type, embeds each document whole through the sink's media path (CAMEL-25027); IngestPipeline gets modality() and contentType(). A configured pipeline's modality and content-type are checked at build time, while the splitter sizes and batch size, which do not apply, are not; the builder setters reject bad values. modality, contentType and the filter options replace the neutral values apache#9152 pins on the sink, so app-wide camel.kamelet.<id>.* properties still cannot change them. The file source's charset has no neutral value, so parser and media pipelines drop an app-wide one before the raw bytes are read. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
28197ef to
92c3c12
Compare
|
Thanks for the review. All five points are fixed in 92c3c12, squashed into the PR commit. Details are in the threads. I reproduced the register and charset issues before fixing them. The deployment tests (48) and JVM ITs (31) pass. On the Camel issues from #9290: filed both, thanks for the nudge:
With filtered keys now held in memory, the first one no longer has the drawback I gave in #9290. Generated with Claude Code on behalf of @JiriOndrusek |
jamesnetherton
left a comment
There was a problem hiding this comment.
Thanks, all five points are addressed. I checked SourceRegister against the file consumer's idempotent flow (eager add/confirm, non-eager add, rollback remove) and it holds in each case. Approving pending CI.
Two optional nits, not blocking:
fileFilteredlooks the register up in the registry for every filtered file; passing theSourceRegisterin directly would avoid that.- The new IT checks the key stays out of the named register, but nothing covers the after-restart behaviour itself.
Please link the two Camel issues here once filed.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of James Netherton
Fixes #9280
Supersedes #9290
filter.*options go to the sink Kamelet, which enforces them. With a parser, the id patterns are also checked before the parse.modality=media(optionalcontent-type) embeds each document whole, as one vector; the builder getsmodality()andcontentType(). Conflicting settings fail the build.Generated with Claude Code on behalf of @JiriOndrusek