OPENNLP-1920: Add dependency-path relation extraction as a typed Document annotation layer - #1238
Draft
krickert wants to merge 33 commits into
Draft
Conversation
For each ordered entity pair the annotator walks the dependency path between the entity heads, up to the lowest common ancestor and down, and emits a typed relation for every registered path pattern that matches, with an optional trigger constraint on the pivot token. Relations reference entities by layer index, never object identity. (cherry picked from commit a4c17a1)
Pattern parsing now matches the rest of the extraction family: no regular expressions anywhere, including configuration parsing. The public steps() accessor exposes the parsed path. (cherry picked from commit f8054b2)
StringUtil.isWhitespace follows the project's Unicode whitespace definition under the default mode, keeping pattern parsing consistent with tokenization instead of the JDK's whitespace notion. (cherry picked from commit 7397232)
…ce range (cherry picked from commit 0663304)
… expectations with the StringUtil semantics of this base
…nstead of a character table
…triggers that could never match
…he project predicate A trigger is compared against a single pivot token, so a trigger containing any whitespace is a rule that can never match; construction now rejects it, closing the half of the never-matching defect the case-mapping fix left open. Type, path, and trigger blankness are judged under the project whitespace definition, which covers the no-break spaces the JDK blank check misses, so a path spelled from them can no longer construct a zero-step dead rule, and the steps of every constructible pattern are guaranteed non-empty. New tests pin the supplementary-plane cased trigger pair and the negative side of the annotator's mapping seam: the JDK-lowercased spelling of the dotted capital I matches nothing.
Replaces the private blank helper with the shared predicate; behavior is identical since it already followed the toolkit whitespace definition.
Add docbkx/relation.xml, wire it into the manual, and cite RelationExtractionExampleTest.
The trigger matches the pivot token's lowercased surface form, not a lemma; say "trigger word". Open the CDATA on its own line to match the sibling chapters and avoid a leading blank line in the rendered listing.
… document syntax - Walk each entity's chain to the root once per entity instead of once per ordered pair. RelationAnnotator.annotate now fills a chains list alongside entityHeads and passes the two chains straight into matchPair, so an entity that takes part in n pairs is walked once rather than n times. matchPair drops the heads and entityHeads parameters it no longer needs and its null-chain guard moves up into the pair loop, where an unreachable head and a cyclic arc set are now the same skip condition. - Extract the path step markers as RelationPattern.UP_STEP and DOWN_STEP and use them in both places that knew the characters: the step validator in the compact constructor and the step assembly in RelationAnnotator.matchPair. The two sites can no longer drift apart from each other or from the manual. - Judge a RelationMention type by StringUtil.isBlank rather than String.isBlank, so a type spelled entirely from no-break spaces is rejected in the mention exactly as it already was in the pattern. The record's javadoc now names the predicate instead of restating the toolkit whitespace definition, and it points at the container's reference-by-layer-and-index rule rather than claiming durability across serialization. - Give the overridden annotate method the javadoc it was missing: the layers it requires, that an absent entity layer reads as empty, the stable pair and pattern ordering that fixes the output order, and the full set of conditions that raise IllegalArgumentException. - Trim the javadoc that argued its own case. RelationPattern's trigger paragraph and the steps and splitSteps comments now state the rule once, and the surviving inline comment in the pair loop explains only why the chains are precomputed, which the code cannot say for itself. - Pin every invalid RelationMention component in a test of its own. The new testRelationMentionValidation asserts the exact message for a null, blank, and no-break space only type, for a negative index, and for a subject equal to its object, replacing the single mention assertion that had been tucked into testPatternValidation. - Turn the three loop-driven tests in RelationPatternTest into parameterized tests, so a failing character is reported as its own case instead of aborting the loop at the first one, and fold testLeadingAndTrailingNoBreakSpacesAreIgnored into testStepsSplitOnUnicodeSpaceSeparators, which already asserts a path fringed with no-break spaces. - Replace the remaining bare Unicode escapes in the tests with the named constants that were already declared for them, and add JDK_LOWERCASED_ISTANBUL and NO_BREAK_SPACE in RelationAnnotatorTest so the dotted capital I and the no-break space read the same way in both test classes. - Add a Pattern syntax section to the relation manual chapter covering the step grammar, the rule that up steps precede down steps, the pivot as the lowest common ancestor, how a multi-token entity's head is chosen, and that a trigger is matched verbatim and so must be lowercase and whitespace free.
Every annotator in the container rejects an absent required layer with a message naming it and accepts present but empty layers as valid input. The absent-entity-layer and empty-layers tests fail until the relation annotator is aligned with that contract.
Validate required layers through DocumentAnnotators.requireLayers so an absent layer is rejected with the shared message and empty layers degrade to an empty relations layer, matching every other annotator. Walk root chains over int arrays instead of boxed lists, fold the repeated dependency layer fixture into one test helper, and cover triggers with leading whitespace.
krickert
added a commit
to ai-pipestream/opennlp
that referenced
this pull request
Aug 22, 2026
For each ordered entity pair the annotator walks the dependency path between the entity heads, up to the lowest common ancestor and down, and emits a typed relation for every registered path pattern that matches, with an optional trigger constraint on the pivot token. Relations reference entities by layer index, never object identity. (cherry picked from commit a4c17a1)
Pattern parsing now matches the rest of the extraction family: no regular expressions anywhere, including configuration parsing. The public steps() accessor exposes the parsed path. (cherry picked from commit f8054b2)
StringUtil.isWhitespace follows the project's Unicode whitespace definition under the default mode, keeping pattern parsing consistent with tokenization instead of the JDK's whitespace notion. (cherry picked from commit 7397232)
…ce range (cherry picked from commit 0663304)
… expectations with the StringUtil semantics of this base
…nstead of a character table
…triggers that could never match
…he project predicate A trigger is compared against a single pivot token, so a trigger containing any whitespace is a rule that can never match; construction now rejects it, closing the half of the never-matching defect the case-mapping fix left open. Type, path, and trigger blankness are judged under the project whitespace definition, which covers the no-break spaces the JDK blank check misses, so a path spelled from them can no longer construct a zero-step dead rule, and the steps of every constructible pattern are guaranteed non-empty. New tests pin the supplementary-plane cased trigger pair and the negative side of the annotator's mapping seam: the JDK-lowercased spelling of the dotted capital I matches nothing.
Replaces the private blank helper with the shared predicate; behavior is identical since it already followed the toolkit whitespace definition.
Add docbkx/relation.xml, wire it into the manual, and cite RelationExtractionExampleTest.
The trigger matches the pivot token's lowercased surface form, not a lemma; say "trigger word". Open the CDATA on its own line to match the sibling chapters and avoid a leading blank line in the rendered listing.
… document syntax - Walk each entity's chain to the root once per entity instead of once per ordered pair. RelationAnnotator.annotate now fills a chains list alongside entityHeads and passes the two chains straight into matchPair, so an entity that takes part in n pairs is walked once rather than n times. matchPair drops the heads and entityHeads parameters it no longer needs and its null-chain guard moves up into the pair loop, where an unreachable head and a cyclic arc set are now the same skip condition. - Extract the path step markers as RelationPattern.UP_STEP and DOWN_STEP and use them in both places that knew the characters: the step validator in the compact constructor and the step assembly in RelationAnnotator.matchPair. The two sites can no longer drift apart from each other or from the manual. - Judge a RelationMention type by StringUtil.isBlank rather than String.isBlank, so a type spelled entirely from no-break spaces is rejected in the mention exactly as it already was in the pattern. The record's javadoc now names the predicate instead of restating the toolkit whitespace definition, and it points at the container's reference-by-layer-and-index rule rather than claiming durability across serialization. - Give the overridden annotate method the javadoc it was missing: the layers it requires, that an absent entity layer reads as empty, the stable pair and pattern ordering that fixes the output order, and the full set of conditions that raise IllegalArgumentException. - Trim the javadoc that argued its own case. RelationPattern's trigger paragraph and the steps and splitSteps comments now state the rule once, and the surviving inline comment in the pair loop explains only why the chains are precomputed, which the code cannot say for itself. - Pin every invalid RelationMention component in a test of its own. The new testRelationMentionValidation asserts the exact message for a null, blank, and no-break space only type, for a negative index, and for a subject equal to its object, replacing the single mention assertion that had been tucked into testPatternValidation. - Turn the three loop-driven tests in RelationPatternTest into parameterized tests, so a failing character is reported as its own case instead of aborting the loop at the first one, and fold testLeadingAndTrailingNoBreakSpacesAreIgnored into testStepsSplitOnUnicodeSpaceSeparators, which already asserts a path fringed with no-break spaces. - Replace the remaining bare Unicode escapes in the tests with the named constants that were already declared for them, and add JDK_LOWERCASED_ISTANBUL and NO_BREAK_SPACE in RelationAnnotatorTest so the dotted capital I and the no-break space read the same way in both test classes. - Add a Pattern syntax section to the relation manual chapter covering the step grammar, the rule that up steps precede down steps, the pivot as the lowest common ancestor, how a multi-token entity's head is chosen, and that a trigger is matched verbatim and so must be lowercase and whitespace free.
Every annotator in the container rejects an absent required layer with a message naming it and accepts present but empty layers as valid input. The absent-entity-layer and empty-layers tests fail until the relation annotator is aligned with that contract.
Validate required layers through DocumentAnnotators.requireLayers so an absent layer is rejected with the shared message and empty layers degrade to an empty relations layer, matching every other annotator. Walk root chains over int arrays instead of boxed lists, fold the repeated dependency layer fixture into one test helper, and cover triggers with leading whitespace.
krickert
added a commit
that referenced
this pull request
Aug 30, 2026
krickert
added a commit
that referenced
this pull request
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stack
This draft depends on OPENNLP-1919, OPENNLP-547, and OPENNLP-1888. It must remain draft until the dependency stack lands.